Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / ext-all-dev.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext
17  * @singleton
18  */
19 (function() {
20     var global = this,
21         objectPrototype = Object.prototype,
22         toString = objectPrototype.toString,
23         enumerables = true,
24         enumerablesTest = { toString: 1 },
25         i;
26
27     if (typeof Ext === 'undefined') {
28         global.Ext = {};
29     }
30
31     Ext.global = global;
32
33     for (i in enumerablesTest) {
34         enumerables = null;
35     }
36
37     if (enumerables) {
38         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
39                        'toLocaleString', 'toString', 'constructor'];
40     }
41
42     /**
43      * An array containing extra enumerables for old browsers
44      * @property {String[]}
45      */
46     Ext.enumerables = enumerables;
47
48     /**
49      * Copies all the properties of config to the specified object.
50      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
51      * {@link Ext.Object#merge} instead.
52      * @param {Object} object The receiver of the properties
53      * @param {Object} config The source of the properties
54      * @param {Object} defaults A different object that will also be applied for default values
55      * @return {Object} returns obj
56      */
57     Ext.apply = function(object, config, defaults) {
58         if (defaults) {
59             Ext.apply(object, defaults);
60         }
61
62         if (object && config && typeof config === 'object') {
63             var i, j, k;
64
65             for (i in config) {
66                 object[i] = config[i];
67             }
68
69             if (enumerables) {
70                 for (j = enumerables.length; j--;) {
71                     k = enumerables[j];
72                     if (config.hasOwnProperty(k)) {
73                         object[k] = config[k];
74                     }
75                 }
76             }
77         }
78
79         return object;
80     };
81
82     Ext.buildSettings = Ext.apply({
83         baseCSSPrefix: 'x-',
84         scopeResetCSS: false
85     }, Ext.buildSettings || {});
86
87     Ext.apply(Ext, {
88         /**
89          * A reusable empty function
90          */
91         emptyFn: function() {},
92
93         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
94
95         /**
96          * Copies all the properties of config to object if they don't already exist.
97          * @param {Object} object The receiver of the properties
98          * @param {Object} config The source of the properties
99          * @return {Object} returns obj
100          */
101         applyIf: function(object, config) {
102             var property;
103
104             if (object) {
105                 for (property in config) {
106                     if (object[property] === undefined) {
107                         object[property] = config[property];
108                     }
109                 }
110             }
111
112             return object;
113         },
114
115         /**
116          * Iterates either an array or an object. This method delegates to
117          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
118          *
119          * @param {Object/Array} object The object or array to be iterated.
120          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
121          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
122          * type that is being iterated.
123          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
124          * Defaults to the object being iterated itself.
125          * @markdown
126          */
127         iterate: function(object, fn, scope) {
128             if (Ext.isEmpty(object)) {
129                 return;
130             }
131
132             if (scope === undefined) {
133                 scope = object;
134             }
135
136             if (Ext.isIterable(object)) {
137                 Ext.Array.each.call(Ext.Array, object, fn, scope);
138             }
139             else {
140                 Ext.Object.each.call(Ext.Object, object, fn, scope);
141             }
142         }
143     });
144
145     Ext.apply(Ext, {
146
147         /**
148          * This method deprecated. Use {@link Ext#define Ext.define} instead.
149          * @method
150          * @param {Function} superclass
151          * @param {Object} overrides
152          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
153          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
154          */
155         extend: function() {
156             // inline overrides
157             var objectConstructor = objectPrototype.constructor,
158                 inlineOverrides = function(o) {
159                 for (var m in o) {
160                     if (!o.hasOwnProperty(m)) {
161                         continue;
162                     }
163                     this[m] = o[m];
164                 }
165             };
166
167             return function(subclass, superclass, overrides) {
168                 // First we check if the user passed in just the superClass with overrides
169                 if (Ext.isObject(superclass)) {
170                     overrides = superclass;
171                     superclass = subclass;
172                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
173                         superclass.apply(this, arguments);
174                     };
175                 }
176
177                 if (!superclass) {
178                     Ext.Error.raise({
179                         sourceClass: 'Ext',
180                         sourceMethod: 'extend',
181                         msg: 'Attempting to extend from a class which has not been loaded on the page.'
182                     });
183                 }
184
185                 // We create a new temporary class
186                 var F = function() {},
187                     subclassProto, superclassProto = superclass.prototype;
188
189                 F.prototype = superclassProto;
190                 subclassProto = subclass.prototype = new F();
191                 subclassProto.constructor = subclass;
192                 subclass.superclass = superclassProto;
193
194                 if (superclassProto.constructor === objectConstructor) {
195                     superclassProto.constructor = superclass;
196                 }
197
198                 subclass.override = function(overrides) {
199                     Ext.override(subclass, overrides);
200                 };
201
202                 subclassProto.override = inlineOverrides;
203                 subclassProto.proto = subclassProto;
204
205                 subclass.override(overrides);
206                 subclass.extend = function(o) {
207                     return Ext.extend(subclass, o);
208                 };
209
210                 return subclass;
211             };
212         }(),
213
214         /**
215          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
216
217     Ext.define('My.cool.Class', {
218         sayHi: function() {
219             alert('Hi!');
220         }
221     }
222
223     Ext.override(My.cool.Class, {
224         sayHi: function() {
225             alert('About to say...');
226
227             this.callOverridden();
228         }
229     });
230
231     var cool = new My.cool.Class();
232     cool.sayHi(); // alerts 'About to say...'
233                   // alerts 'Hi!'
234
235          * Please note that `this.callOverridden()` only works if the class was previously
236          * created with {@link Ext#define)
237          *
238          * @param {Object} cls The class to override
239          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
240          * containing one or more methods.
241          * @method override
242          * @markdown
243          */
244         override: function(cls, overrides) {
245             if (cls.prototype.$className) {
246                 return cls.override(overrides);
247             }
248             else {
249                 Ext.apply(cls.prototype, overrides);
250             }
251         }
252     });
253
254     // A full set of static methods to do type checking
255     Ext.apply(Ext, {
256
257         /**
258          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
259          * value (second argument) otherwise.
260          *
261          * @param {Object} value The value to test
262          * @param {Object} defaultValue The value to return if the original value is empty
263          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
264          * @return {Object} value, if non-empty, else defaultValue
265          */
266         valueFrom: function(value, defaultValue, allowBlank){
267             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
268         },
269
270         /**
271          * Returns the type of the given variable in string format. List of possible values are:
272          *
273          * - `undefined`: If the given value is `undefined`
274          * - `null`: If the given value is `null`
275          * - `string`: If the given value is a string
276          * - `number`: If the given value is a number
277          * - `boolean`: If the given value is a boolean value
278          * - `date`: If the given value is a `Date` object
279          * - `function`: If the given value is a function reference
280          * - `object`: If the given value is an object
281          * - `array`: If the given value is an array
282          * - `regexp`: If the given value is a regular expression
283          * - `element`: If the given value is a DOM Element
284          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
285          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
286          *
287          * @param {Object} value
288          * @return {String}
289          * @markdown
290          */
291         typeOf: function(value) {
292             if (value === null) {
293                 return 'null';
294             }
295
296             var type = typeof value;
297
298             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
299                 return type;
300             }
301
302             var typeToString = toString.call(value);
303
304             switch(typeToString) {
305                 case '[object Array]':
306                     return 'array';
307                 case '[object Date]':
308                     return 'date';
309                 case '[object Boolean]':
310                     return 'boolean';
311                 case '[object Number]':
312                     return 'number';
313                 case '[object RegExp]':
314                     return 'regexp';
315             }
316
317             if (type === 'function') {
318                 return 'function';
319             }
320
321             if (type === 'object') {
322                 if (value.nodeType !== undefined) {
323                     if (value.nodeType === 3) {
324                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
325                     }
326                     else {
327                         return 'element';
328                     }
329                 }
330
331                 return 'object';
332             }
333
334             Ext.Error.raise({
335                 sourceClass: 'Ext',
336                 sourceMethod: 'typeOf',
337                 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
338             });
339         },
340
341         /**
342          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
343          *
344          * - `null`
345          * - `undefined`
346          * - a zero-length array
347          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
348          *
349          * @param {Object} value The value to test
350          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
351          * @return {Boolean}
352          * @markdown
353          */
354         isEmpty: function(value, allowEmptyString) {
355             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
356         },
357
358         /**
359          * Returns true if the passed value is a JavaScript Array, false otherwise.
360          *
361          * @param {Object} target The target to test
362          * @return {Boolean}
363          * @method
364          */
365         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
366             return toString.call(value) === '[object Array]';
367         },
368
369         /**
370          * Returns true if the passed value is a JavaScript Date object, false otherwise.
371          * @param {Object} object The object to test
372          * @return {Boolean}
373          */
374         isDate: function(value) {
375             return toString.call(value) === '[object Date]';
376         },
377
378         /**
379          * Returns true if the passed value is a JavaScript Object, false otherwise.
380          * @param {Object} value The value to test
381          * @return {Boolean}
382          * @method
383          */
384         isObject: (toString.call(null) === '[object Object]') ?
385         function(value) {
386             // check ownerDocument here as well to exclude DOM nodes
387             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
388         } :
389         function(value) {
390             return toString.call(value) === '[object Object]';
391         },
392
393         /**
394          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
395          * @param {Object} value The value to test
396          * @return {Boolean}
397          */
398         isPrimitive: function(value) {
399             var type = typeof value;
400
401             return type === 'string' || type === 'number' || type === 'boolean';
402         },
403
404         /**
405          * Returns true if the passed value is a JavaScript Function, false otherwise.
406          * @param {Object} value The value to test
407          * @return {Boolean}
408          * @method
409          */
410         isFunction:
411         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
412         // Object.prorotype.toString (slower)
413         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
414             return toString.call(value) === '[object Function]';
415         } : function(value) {
416             return typeof value === 'function';
417         },
418
419         /**
420          * Returns true if the passed value is a number. Returns false for non-finite numbers.
421          * @param {Object} value The value to test
422          * @return {Boolean}
423          */
424         isNumber: function(value) {
425             return typeof value === 'number' && isFinite(value);
426         },
427
428         /**
429          * Validates that a value is numeric.
430          * @param {Object} value Examples: 1, '1', '2.34'
431          * @return {Boolean} True if numeric, false otherwise
432          */
433         isNumeric: function(value) {
434             return !isNaN(parseFloat(value)) && isFinite(value);
435         },
436
437         /**
438          * Returns true if the passed value is a string.
439          * @param {Object} value The value to test
440          * @return {Boolean}
441          */
442         isString: function(value) {
443             return typeof value === 'string';
444         },
445
446         /**
447          * Returns true if the passed value is a boolean.
448          *
449          * @param {Object} value The value to test
450          * @return {Boolean}
451          */
452         isBoolean: function(value) {
453             return typeof value === 'boolean';
454         },
455
456         /**
457          * Returns true if the passed value is an HTMLElement
458          * @param {Object} value The value to test
459          * @return {Boolean}
460          */
461         isElement: function(value) {
462             return value ? value.nodeType === 1 : false;
463         },
464
465         /**
466          * Returns true if the passed value is a TextNode
467          * @param {Object} value The value to test
468          * @return {Boolean}
469          */
470         isTextNode: function(value) {
471             return value ? value.nodeName === "#text" : false;
472         },
473
474         /**
475          * Returns true if the passed value is defined.
476          * @param {Object} value The value to test
477          * @return {Boolean}
478          */
479         isDefined: function(value) {
480             return typeof value !== 'undefined';
481         },
482
483         /**
484          * Returns true if the passed value is iterable, false otherwise
485          * @param {Object} value The value to test
486          * @return {Boolean}
487          */
488         isIterable: function(value) {
489             return (value && typeof value !== 'string') ? value.length !== undefined : false;
490         }
491     });
492
493     Ext.apply(Ext, {
494
495         /**
496          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
497          * @param {Object} item The variable to clone
498          * @return {Object} clone
499          */
500         clone: function(item) {
501             if (item === null || item === undefined) {
502                 return item;
503             }
504
505             // DOM nodes
506             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
507             // recursively
508             if (item.nodeType && item.cloneNode) {
509                 return item.cloneNode(true);
510             }
511
512             var type = toString.call(item);
513
514             // Date
515             if (type === '[object Date]') {
516                 return new Date(item.getTime());
517             }
518
519             var i, j, k, clone, key;
520
521             // Array
522             if (type === '[object Array]') {
523                 i = item.length;
524
525                 clone = [];
526
527                 while (i--) {
528                     clone[i] = Ext.clone(item[i]);
529                 }
530             }
531             // Object
532             else if (type === '[object Object]' && item.constructor === Object) {
533                 clone = {};
534
535                 for (key in item) {
536                     clone[key] = Ext.clone(item[key]);
537                 }
538
539                 if (enumerables) {
540                     for (j = enumerables.length; j--;) {
541                         k = enumerables[j];
542                         clone[k] = item[k];
543                     }
544                 }
545             }
546
547             return clone || item;
548         },
549
550         /**
551          * @private
552          * Generate a unique reference of Ext in the global scope, useful for sandboxing
553          */
554         getUniqueGlobalNamespace: function() {
555             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
556
557             if (uniqueGlobalNamespace === undefined) {
558                 var i = 0;
559
560                 do {
561                     uniqueGlobalNamespace = 'ExtBox' + (++i);
562                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
563
564                 Ext.global[uniqueGlobalNamespace] = Ext;
565                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
566             }
567
568             return uniqueGlobalNamespace;
569         },
570
571         /**
572          * @private
573          */
574         functionFactory: function() {
575             var args = Array.prototype.slice.call(arguments);
576
577             if (args.length > 0) {
578                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
579                     args[args.length - 1];
580             }
581
582             return Function.prototype.constructor.apply(Function.prototype, args);
583         }
584     });
585
586     /**
587      * Old alias to {@link Ext#typeOf}
588      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
589      * @method
590      * @alias Ext#typeOf
591      */
592     Ext.type = Ext.typeOf;
593
594 })();
595
596 /**
597  * @author Jacky Nguyen <jacky@sencha.com>
598  * @docauthor Jacky Nguyen <jacky@sencha.com>
599  * @class Ext.Version
600  *
601  * A utility class that wrap around a string version number and provide convenient
602  * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
603
604     var version = new Ext.Version('1.0.2beta');
605     console.log("Version is " + version); // Version is 1.0.2beta
606
607     console.log(version.getMajor()); // 1
608     console.log(version.getMinor()); // 0
609     console.log(version.getPatch()); // 2
610     console.log(version.getBuild()); // 0
611     console.log(version.getRelease()); // beta
612
613     console.log(version.isGreaterThan('1.0.1')); // True
614     console.log(version.isGreaterThan('1.0.2alpha')); // True
615     console.log(version.isGreaterThan('1.0.2RC')); // False
616     console.log(version.isGreaterThan('1.0.2')); // False
617     console.log(version.isLessThan('1.0.2')); // True
618
619     console.log(version.match(1.0)); // True
620     console.log(version.match('1.0.2')); // True
621
622  * @markdown
623  */
624 (function() {
625
626 // Current core version
627 var version = '4.0.7', Version;
628     Ext.Version = Version = Ext.extend(Object, {
629
630         /**
631          * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
632          * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
633          * @return {Ext.Version} this
634          */
635         constructor: function(version) {
636             var parts, releaseStartIndex;
637
638             if (version instanceof Version) {
639                 return version;
640             }
641
642             this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
643
644             releaseStartIndex = this.version.search(/([^\d\.])/);
645
646             if (releaseStartIndex !== -1) {
647                 this.release = this.version.substr(releaseStartIndex, version.length);
648                 this.shortVersion = this.version.substr(0, releaseStartIndex);
649             }
650
651             this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
652
653             parts = this.version.split('.');
654
655             this.major = parseInt(parts.shift() || 0, 10);
656             this.minor = parseInt(parts.shift() || 0, 10);
657             this.patch = parseInt(parts.shift() || 0, 10);
658             this.build = parseInt(parts.shift() || 0, 10);
659
660             return this;
661         },
662
663         /**
664          * Override the native toString method
665          * @private
666          * @return {String} version
667          */
668         toString: function() {
669             return this.version;
670         },
671
672         /**
673          * Override the native valueOf method
674          * @private
675          * @return {String} version
676          */
677         valueOf: function() {
678             return this.version;
679         },
680
681         /**
682          * Returns the major component value
683          * @return {Number} major
684          */
685         getMajor: function() {
686             return this.major || 0;
687         },
688
689         /**
690          * Returns the minor component value
691          * @return {Number} minor
692          */
693         getMinor: function() {
694             return this.minor || 0;
695         },
696
697         /**
698          * Returns the patch component value
699          * @return {Number} patch
700          */
701         getPatch: function() {
702             return this.patch || 0;
703         },
704
705         /**
706          * Returns the build component value
707          * @return {Number} build
708          */
709         getBuild: function() {
710             return this.build || 0;
711         },
712
713         /**
714          * Returns the release component value
715          * @return {Number} release
716          */
717         getRelease: function() {
718             return this.release || '';
719         },
720
721         /**
722          * Returns whether this version if greater than the supplied argument
723          * @param {String/Number} target The version to compare with
724          * @return {Boolean} True if this version if greater than the target, false otherwise
725          */
726         isGreaterThan: function(target) {
727             return Version.compare(this.version, target) === 1;
728         },
729
730         /**
731          * Returns whether this version if smaller than the supplied argument
732          * @param {String/Number} target The version to compare with
733          * @return {Boolean} True if this version if smaller than the target, false otherwise
734          */
735         isLessThan: function(target) {
736             return Version.compare(this.version, target) === -1;
737         },
738
739         /**
740          * Returns whether this version equals to the supplied argument
741          * @param {String/Number} target The version to compare with
742          * @return {Boolean} True if this version equals to the target, false otherwise
743          */
744         equals: function(target) {
745             return Version.compare(this.version, target) === 0;
746         },
747
748         /**
749          * Returns whether this version matches the supplied argument. Example:
750          * <pre><code>
751          * var version = new Ext.Version('1.0.2beta');
752          * console.log(version.match(1)); // True
753          * console.log(version.match(1.0)); // True
754          * console.log(version.match('1.0.2')); // True
755          * console.log(version.match('1.0.2RC')); // False
756          * </code></pre>
757          * @param {String/Number} target The version to compare with
758          * @return {Boolean} True if this version matches the target, false otherwise
759          */
760         match: function(target) {
761             target = String(target);
762             return this.version.substr(0, target.length) === target;
763         },
764
765         /**
766          * Returns this format: [major, minor, patch, build, release]. Useful for comparison
767          * @return {Number[]}
768          */
769         toArray: function() {
770             return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
771         },
772
773         /**
774          * Returns shortVersion version without dots and release
775          * @return {String}
776          */
777         getShortVersion: function() {
778             return this.shortVersion;
779         }
780     });
781
782     Ext.apply(Version, {
783         // @private
784         releaseValueMap: {
785             'dev': -6,
786             'alpha': -5,
787             'a': -5,
788             'beta': -4,
789             'b': -4,
790             'rc': -3,
791             '#': -2,
792             'p': -1,
793             'pl': -1
794         },
795
796         /**
797          * Converts a version component to a comparable value
798          *
799          * @static
800          * @param {Object} value The value to convert
801          * @return {Object}
802          */
803         getComponentValue: function(value) {
804             return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
805         },
806
807         /**
808          * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
809          * they are handled in the following order:
810          * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
811          *
812          * @static
813          * @param {String} current The current version to compare to
814          * @param {String} target The target version to compare to
815          * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
816          */
817         compare: function(current, target) {
818             var currentValue, targetValue, i;
819
820             current = new Version(current).toArray();
821             target = new Version(target).toArray();
822
823             for (i = 0; i < Math.max(current.length, target.length); i++) {
824                 currentValue = this.getComponentValue(current[i]);
825                 targetValue = this.getComponentValue(target[i]);
826
827                 if (currentValue < targetValue) {
828                     return -1;
829                 } else if (currentValue > targetValue) {
830                     return 1;
831                 }
832             }
833
834             return 0;
835         }
836     });
837
838     Ext.apply(Ext, {
839         /**
840          * @private
841          */
842         versions: {},
843
844         /**
845          * @private
846          */
847         lastRegisteredVersion: null,
848
849         /**
850          * Set version number for the given package name.
851          *
852          * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
853          * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
854          * @return {Ext}
855          */
856         setVersion: function(packageName, version) {
857             Ext.versions[packageName] = new Version(version);
858             Ext.lastRegisteredVersion = Ext.versions[packageName];
859
860             return this;
861         },
862
863         /**
864          * Get the version number of the supplied package name; will return the last registered version
865          * (last Ext.setVersion call) if there's no package name given.
866          *
867          * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
868          * @return {Ext.Version} The version
869          */
870         getVersion: function(packageName) {
871             if (packageName === undefined) {
872                 return Ext.lastRegisteredVersion;
873             }
874
875             return Ext.versions[packageName];
876         },
877
878         /**
879          * Create a closure for deprecated code.
880          *
881     // This means Ext.oldMethod is only supported in 4.0.0beta and older.
882     // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
883     // the closure will not be invoked
884     Ext.deprecate('extjs', '4.0.0beta', function() {
885         Ext.oldMethod = Ext.newMethod;
886
887         ...
888     });
889
890          * @param {String} packageName The package name
891          * @param {String} since The last version before it's deprecated
892          * @param {Function} closure The callback function to be executed with the specified version is less than the current version
893          * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
894          * @markdown
895          */
896         deprecate: function(packageName, since, closure, scope) {
897             if (Version.compare(Ext.getVersion(packageName), since) < 1) {
898                 closure.call(scope);
899             }
900         }
901     }); // End Versioning
902
903     Ext.setVersion('core', version);
904
905 })();
906
907 /**
908  * @class Ext.String
909  *
910  * A collection of useful static methods to deal with strings
911  * @singleton
912  */
913
914 Ext.String = {
915     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,
916     escapeRe: /('|\\)/g,
917     formatRe: /\{(\d+)\}/g,
918     escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
919
920     /**
921      * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
922      * @param {String} value The string to encode
923      * @return {String} The encoded text
924      * @method
925      */
926     htmlEncode: (function() {
927         var entities = {
928             '&': '&amp;',
929             '>': '&gt;',
930             '<': '&lt;',
931             '"': '&quot;'
932         }, keys = [], p, regex;
933         
934         for (p in entities) {
935             keys.push(p);
936         }
937         
938         regex = new RegExp('(' + keys.join('|') + ')', 'g');
939         
940         return function(value) {
941             return (!value) ? value : String(value).replace(regex, function(match, capture) {
942                 return entities[capture];    
943             });
944         };
945     })(),
946
947     /**
948      * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
949      * @param {String} value The string to decode
950      * @return {String} The decoded text
951      * @method
952      */
953     htmlDecode: (function() {
954         var entities = {
955             '&amp;': '&',
956             '&gt;': '>',
957             '&lt;': '<',
958             '&quot;': '"'
959         }, keys = [], p, regex;
960         
961         for (p in entities) {
962             keys.push(p);
963         }
964         
965         regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
966         
967         return function(value) {
968             return (!value) ? value : String(value).replace(regex, function(match, capture) {
969                 if (capture in entities) {
970                     return entities[capture];
971                 } else {
972                     return String.fromCharCode(parseInt(capture.substr(2), 10));
973                 }
974             });
975         };
976     })(),
977
978     /**
979      * Appends content to the query string of a URL, handling logic for whether to place
980      * a question mark or ampersand.
981      * @param {String} url The URL to append to.
982      * @param {String} string The content to append to the URL.
983      * @return (String) The resulting URL
984      */
985     urlAppend : function(url, string) {
986         if (!Ext.isEmpty(string)) {
987             return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
988         }
989
990         return url;
991     },
992
993     /**
994      * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
995      * @example
996 var s = '  foo bar  ';
997 alert('-' + s + '-');         //alerts "- foo bar -"
998 alert('-' + Ext.String.trim(s) + '-');  //alerts "-foo bar-"
999
1000      * @param {String} string The string to escape
1001      * @return {String} The trimmed string
1002      */
1003     trim: function(string) {
1004         return string.replace(Ext.String.trimRegex, "");
1005     },
1006
1007     /**
1008      * Capitalize the given string
1009      * @param {String} string
1010      * @return {String}
1011      */
1012     capitalize: function(string) {
1013         return string.charAt(0).toUpperCase() + string.substr(1);
1014     },
1015
1016     /**
1017      * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1018      * @param {String} value The string to truncate
1019      * @param {Number} length The maximum length to allow before truncating
1020      * @param {Boolean} word True to try to find a common word break
1021      * @return {String} The converted text
1022      */
1023     ellipsis: function(value, len, word) {
1024         if (value && value.length > len) {
1025             if (word) {
1026                 var vs = value.substr(0, len - 2),
1027                 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1028                 if (index !== -1 && index >= (len - 15)) {
1029                     return vs.substr(0, index) + "...";
1030                 }
1031             }
1032             return value.substr(0, len - 3) + "...";
1033         }
1034         return value;
1035     },
1036
1037     /**
1038      * Escapes the passed string for use in a regular expression
1039      * @param {String} string
1040      * @return {String}
1041      */
1042     escapeRegex: function(string) {
1043         return string.replace(Ext.String.escapeRegexRe, "\\$1");
1044     },
1045
1046     /**
1047      * Escapes the passed string for ' and \
1048      * @param {String} string The string to escape
1049      * @return {String} The escaped string
1050      */
1051     escape: function(string) {
1052         return string.replace(Ext.String.escapeRe, "\\$1");
1053     },
1054
1055     /**
1056      * Utility function that allows you to easily switch a string between two alternating values.  The passed value
1057      * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
1058      * they are already different, the first value passed in is returned.  Note that this method returns the new value
1059      * but does not change the current string.
1060      * <pre><code>
1061     // alternate sort directions
1062     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1063
1064     // instead of conditional logic:
1065     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1066        </code></pre>
1067      * @param {String} string The current string
1068      * @param {String} value The value to compare to the current string
1069      * @param {String} other The new value to use if the string already equals the first value passed in
1070      * @return {String} The new value
1071      */
1072     toggle: function(string, value, other) {
1073         return string === value ? other : value;
1074     },
1075
1076     /**
1077      * Pads the left side of a string with a specified character.  This is especially useful
1078      * for normalizing number and date strings.  Example usage:
1079      *
1080      * <pre><code>
1081 var s = Ext.String.leftPad('123', 5, '0');
1082 // s now contains the string: '00123'
1083        </code></pre>
1084      * @param {String} string The original string
1085      * @param {Number} size The total length of the output string
1086      * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1087      * @return {String} The padded string
1088      */
1089     leftPad: function(string, size, character) {
1090         var result = String(string);
1091         character = character || " ";
1092         while (result.length < size) {
1093             result = character + result;
1094         }
1095         return result;
1096     },
1097
1098     /**
1099      * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
1100      * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
1101      * <pre><code>
1102 var cls = 'my-class', text = 'Some text';
1103 var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
1104 // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
1105        </code></pre>
1106      * @param {String} string The tokenized string to be formatted
1107      * @param {String} value1 The value to replace token {0}
1108      * @param {String} value2 Etc...
1109      * @return {String} The formatted string
1110      */
1111     format: function(format) {
1112         var args = Ext.Array.toArray(arguments, 1);
1113         return format.replace(Ext.String.formatRe, function(m, i) {
1114             return args[i];
1115         });
1116     },
1117
1118     /**
1119      * Returns a string with a specified number of repititions a given string pattern.
1120      * The pattern be separated by a different string.
1121      *
1122      *      var s = Ext.String.repeat('---', 4); // = '------------'
1123      *      var t = Ext.String.repeat('--', 3, '/'); // = '--/--/--'
1124      *
1125      * @param {String} pattern The pattern to repeat.
1126      * @param {Number} count The number of times to repeat the pattern (may be 0).
1127      * @param {String} sep An option string to separate each pattern.
1128      */
1129     repeat: function(pattern, count, sep) {
1130         for (var buf = [], i = count; i--; ) {
1131             buf.push(pattern);
1132         }
1133         return buf.join(sep || '');
1134     }
1135 };
1136
1137 /**
1138  * @class Ext.Number
1139  *
1140  * A collection of useful static methods to deal with numbers
1141  * @singleton
1142  */
1143
1144 (function() {
1145
1146 var isToFixedBroken = (0.9).toFixed() !== '1';
1147
1148 Ext.Number = {
1149     /**
1150      * Checks whether or not the passed number is within a desired range.  If the number is already within the
1151      * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1152      * exceeded. Note that this method returns the constrained value but does not change the current number.
1153      * @param {Number} number The number to check
1154      * @param {Number} min The minimum number in the range
1155      * @param {Number} max The maximum number in the range
1156      * @return {Number} The constrained value if outside the range, otherwise the current value
1157      */
1158     constrain: function(number, min, max) {
1159         number = parseFloat(number);
1160
1161         if (!isNaN(min)) {
1162             number = Math.max(number, min);
1163         }
1164         if (!isNaN(max)) {
1165             number = Math.min(number, max);
1166         }
1167         return number;
1168     },
1169
1170     /**
1171      * Snaps the passed number between stopping points based upon a passed increment value.
1172      * @param {Number} value The unsnapped value.
1173      * @param {Number} increment The increment by which the value must move.
1174      * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
1175      * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
1176      * @return {Number} The value of the nearest snap target.
1177      */
1178     snap : function(value, increment, minValue, maxValue) {
1179         var newValue = value,
1180             m;
1181
1182         if (!(increment && value)) {
1183             return value;
1184         }
1185         m = value % increment;
1186         if (m !== 0) {
1187             newValue -= m;
1188             if (m * 2 >= increment) {
1189                 newValue += increment;
1190             } else if (m * 2 < -increment) {
1191                 newValue -= increment;
1192             }
1193         }
1194         return Ext.Number.constrain(newValue, minValue,  maxValue);
1195     },
1196
1197     /**
1198      * Formats a number using fixed-point notation
1199      * @param {Number} value The number to format
1200      * @param {Number} precision The number of digits to show after the decimal point
1201      */
1202     toFixed: function(value, precision) {
1203         if (isToFixedBroken) {
1204             precision = precision || 0;
1205             var pow = Math.pow(10, precision);
1206             return (Math.round(value * pow) / pow).toFixed(precision);
1207         }
1208
1209         return value.toFixed(precision);
1210     },
1211
1212     /**
1213      * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1214      * it is not.
1215
1216 Ext.Number.from('1.23', 1); // returns 1.23
1217 Ext.Number.from('abc', 1); // returns 1
1218
1219      * @param {Object} value
1220      * @param {Number} defaultValue The value to return if the original value is non-numeric
1221      * @return {Number} value, if numeric, defaultValue otherwise
1222      */
1223     from: function(value, defaultValue) {
1224         if (isFinite(value)) {
1225             value = parseFloat(value);
1226         }
1227
1228         return !isNaN(value) ? value : defaultValue;
1229     }
1230 };
1231
1232 })();
1233
1234 /**
1235  * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
1236  * @member Ext
1237  * @method num
1238  * @alias Ext.Number#from
1239  */
1240 Ext.num = function() {
1241     return Ext.Number.from.apply(this, arguments);
1242 };
1243 /**
1244  * @class Ext.Array
1245  * @singleton
1246  * @author Jacky Nguyen <jacky@sencha.com>
1247  * @docauthor Jacky Nguyen <jacky@sencha.com>
1248  *
1249  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1250  */
1251 (function() {
1252
1253     var arrayPrototype = Array.prototype,
1254         slice = arrayPrototype.slice,
1255         supportsSplice = function () {
1256             var array = [],
1257                 lengthBefore,
1258                 j = 20;
1259
1260             if (!array.splice) {
1261                 return false;
1262             }
1263
1264             // This detects a bug in IE8 splice method:
1265             // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
1266
1267             while (j--) {
1268                 array.push("A");
1269             }
1270
1271             array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
1272
1273             lengthBefore = array.length; //41
1274             array.splice(13, 0, "XXX"); // add one element
1275
1276             if (lengthBefore+1 != array.length) {
1277                 return false;
1278             }
1279             // end IE8 bug
1280
1281             return true;
1282         }(),
1283         supportsForEach = 'forEach' in arrayPrototype,
1284         supportsMap = 'map' in arrayPrototype,
1285         supportsIndexOf = 'indexOf' in arrayPrototype,
1286         supportsEvery = 'every' in arrayPrototype,
1287         supportsSome = 'some' in arrayPrototype,
1288         supportsFilter = 'filter' in arrayPrototype,
1289         supportsSort = function() {
1290             var a = [1,2,3,4,5].sort(function(){ return 0; });
1291             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1292         }(),
1293         supportsSliceOnNodeList = true,
1294         ExtArray;
1295
1296     try {
1297         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1298         if (typeof document !== 'undefined') {
1299             slice.call(document.getElementsByTagName('body'));
1300         }
1301     } catch (e) {
1302         supportsSliceOnNodeList = false;
1303     }
1304
1305     function fixArrayIndex (array, index) {
1306         return (index < 0) ? Math.max(0, array.length + index)
1307                            : Math.min(array.length, index);
1308     }
1309
1310     /*
1311     Does the same work as splice, but with a slightly more convenient signature. The splice
1312     method has bugs in IE8, so this is the implementation we use on that platform.
1313
1314     The rippling of items in the array can be tricky. Consider two use cases:
1315
1316                   index=2
1317                   removeCount=2
1318                  /=====\
1319         +---+---+---+---+---+---+---+---+
1320         | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1321         +---+---+---+---+---+---+---+---+
1322                          /  \/  \/  \/  \
1323                         /   /\  /\  /\   \
1324                        /   /  \/  \/  \   +--------------------------+
1325                       /   /   /\  /\   +--------------------------+   \
1326                      /   /   /  \/  +--------------------------+   \   \
1327                     /   /   /   /+--------------------------+   \   \   \
1328                    /   /   /   /                             \   \   \   \
1329                   v   v   v   v                               v   v   v   v
1330         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1331         | 0 | 1 | 4 | 5 | 6 | 7 |       | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
1332         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1333         A                               B        \=========/
1334                                                  insert=[a,b,c]
1335
1336     In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
1337     that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
1338     must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
1339     */
1340     function replaceSim (array, index, removeCount, insert) {
1341         var add = insert ? insert.length : 0,
1342             length = array.length,
1343             pos = fixArrayIndex(array, index);
1344
1345         // we try to use Array.push when we can for efficiency...
1346         if (pos === length) {
1347             if (add) {
1348                 array.push.apply(array, insert);
1349             }
1350         } else {
1351             var remove = Math.min(removeCount, length - pos),
1352                 tailOldPos = pos + remove,
1353                 tailNewPos = tailOldPos + add - remove,
1354                 tailCount = length - tailOldPos,
1355                 lengthAfterRemove = length - remove,
1356                 i;
1357
1358             if (tailNewPos < tailOldPos) { // case A
1359                 for (i = 0; i < tailCount; ++i) {
1360                     array[tailNewPos+i] = array[tailOldPos+i];
1361                 }
1362             } else if (tailNewPos > tailOldPos) { // case B
1363                 for (i = tailCount; i--; ) {
1364                     array[tailNewPos+i] = array[tailOldPos+i];
1365                 }
1366             } // else, add == remove (nothing to do)
1367
1368             if (add && pos === lengthAfterRemove) {
1369                 array.length = lengthAfterRemove; // truncate array
1370                 array.push.apply(array, insert);
1371             } else {
1372                 array.length = lengthAfterRemove + add; // reserves space
1373                 for (i = 0; i < add; ++i) {
1374                     array[pos+i] = insert[i];
1375                 }
1376             }
1377         }
1378
1379         return array;
1380     }
1381
1382     function replaceNative (array, index, removeCount, insert) {
1383         if (insert && insert.length) {
1384             if (index < array.length) {
1385                 array.splice.apply(array, [index, removeCount].concat(insert));
1386             } else {
1387                 array.push.apply(array, insert);
1388             }
1389         } else {
1390             array.splice(index, removeCount);
1391         }
1392         return array;
1393     }
1394
1395     function eraseSim (array, index, removeCount) {
1396         return replaceSim(array, index, removeCount);
1397     }
1398
1399     function eraseNative (array, index, removeCount) {
1400         array.splice(index, removeCount);
1401         return array;
1402     }
1403
1404     function spliceSim (array, index, removeCount) {
1405         var pos = fixArrayIndex(array, index),
1406             removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
1407
1408         if (arguments.length < 4) {
1409             replaceSim(array, pos, removeCount);
1410         } else {
1411             replaceSim(array, pos, removeCount, slice.call(arguments, 3));
1412         }
1413
1414         return removed;
1415     }
1416
1417     function spliceNative (array) {
1418         return array.splice.apply(array, slice.call(arguments, 1));
1419     }
1420
1421     var erase = supportsSplice ? eraseNative : eraseSim,
1422         replace = supportsSplice ? replaceNative : replaceSim,
1423         splice = supportsSplice ? spliceNative : spliceSim;
1424
1425     // NOTE: from here on, use erase, replace or splice (not native methods)...
1426
1427     ExtArray = Ext.Array = {
1428         /**
1429          * Iterates an array or an iterable value and invoke the given callback function for each item.
1430          *
1431          *     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1432          *
1433          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1434          *         console.log(name);
1435          *     });
1436          *
1437          *     var sum = function() {
1438          *         var sum = 0;
1439          *
1440          *         Ext.Array.each(arguments, function(value) {
1441          *             sum += value;
1442          *         });
1443          *
1444          *         return sum;
1445          *     };
1446          *
1447          *     sum(1, 2, 3); // returns 6
1448          *
1449          * The iteration can be stopped by returning false in the function callback.
1450          *
1451          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1452          *         if (name === 'Singapore') {
1453          *             return false; // break here
1454          *         }
1455          *     });
1456          *
1457          * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
1458          *
1459          * @param {Array/NodeList/Object} iterable The value to be iterated. If this
1460          * argument is not iterable, the callback function is called once.
1461          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1462          * the current `index`.
1463          * @param {Object} fn.item The item at the current `index` in the passed `array`
1464          * @param {Number} fn.index The current `index` within the `array`
1465          * @param {Array} fn.allItems The `array` itself which was passed as the first argument
1466          * @param {Boolean} fn.return Return false to stop iteration.
1467          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1468          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1469          * Defaults false
1470          * @return {Boolean} See description for the `fn` parameter.
1471          */
1472         each: function(array, fn, scope, reverse) {
1473             array = ExtArray.from(array);
1474
1475             var i,
1476                 ln = array.length;
1477
1478             if (reverse !== true) {
1479                 for (i = 0; i < ln; i++) {
1480                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1481                         return i;
1482                     }
1483                 }
1484             }
1485             else {
1486                 for (i = ln - 1; i > -1; i--) {
1487                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1488                         return i;
1489                     }
1490                 }
1491             }
1492
1493             return true;
1494         },
1495
1496         /**
1497          * Iterates an array and invoke the given callback function for each item. Note that this will simply
1498          * delegate to the native Array.prototype.forEach method if supported. It doesn't support stopping the
1499          * iteration by returning false in the callback function like {@link Ext.Array#each}. However, performance
1500          * could be much better in modern browsers comparing with {@link Ext.Array#each}
1501          *
1502          * @param {Array} array The array to iterate
1503          * @param {Function} fn The callback function.
1504          * @param {Object} fn.item The item at the current `index` in the passed `array`
1505          * @param {Number} fn.index The current `index` within the `array`
1506          * @param {Array}  fn.allItems The `array` itself which was passed as the first argument
1507          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1508          */
1509         forEach: function(array, fn, scope) {
1510             if (supportsForEach) {
1511                 return array.forEach(fn, scope);
1512             }
1513
1514             var i = 0,
1515                 ln = array.length;
1516
1517             for (; i < ln; i++) {
1518                 fn.call(scope, array[i], i, array);
1519             }
1520         },
1521
1522         /**
1523          * Get the index of the provided `item` in the given `array`, a supplement for the
1524          * missing arrayPrototype.indexOf in Internet Explorer.
1525          *
1526          * @param {Array} array The array to check
1527          * @param {Object} item The item to look for
1528          * @param {Number} from (Optional) The index at which to begin the search
1529          * @return {Number} The index of item in the array (or -1 if it is not found)
1530          */
1531         indexOf: function(array, item, from) {
1532             if (supportsIndexOf) {
1533                 return array.indexOf(item, from);
1534             }
1535
1536             var i, length = array.length;
1537
1538             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1539                 if (array[i] === item) {
1540                     return i;
1541                 }
1542             }
1543
1544             return -1;
1545         },
1546
1547         /**
1548          * Checks whether or not the given `array` contains the specified `item`
1549          *
1550          * @param {Array} array The array to check
1551          * @param {Object} item The item to look for
1552          * @return {Boolean} True if the array contains the item, false otherwise
1553          */
1554         contains: function(array, item) {
1555             if (supportsIndexOf) {
1556                 return array.indexOf(item) !== -1;
1557             }
1558
1559             var i, ln;
1560
1561             for (i = 0, ln = array.length; i < ln; i++) {
1562                 if (array[i] === item) {
1563                     return true;
1564                 }
1565             }
1566
1567             return false;
1568         },
1569
1570         /**
1571          * Converts any iterable (numeric indices and a length property) into a true array.
1572          *
1573          *     function test() {
1574          *         var args = Ext.Array.toArray(arguments),
1575          *             fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1576          *
1577          *         alert(args.join(' '));
1578          *         alert(fromSecondToLastArgs.join(' '));
1579          *     }
1580          *
1581          *     test('just', 'testing', 'here'); // alerts 'just testing here';
1582          *                                      // alerts 'testing here';
1583          *
1584          *     Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1585          *     Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1586          *     Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1587          *
1588          * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
1589          *
1590          * @param {Object} iterable the iterable object to be turned into a true Array.
1591          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1592          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1593          * index of the iterable value
1594          * @return {Array} array
1595          */
1596         toArray: function(iterable, start, end){
1597             if (!iterable || !iterable.length) {
1598                 return [];
1599             }
1600
1601             if (typeof iterable === 'string') {
1602                 iterable = iterable.split('');
1603             }
1604
1605             if (supportsSliceOnNodeList) {
1606                 return slice.call(iterable, start || 0, end || iterable.length);
1607             }
1608
1609             var array = [],
1610                 i;
1611
1612             start = start || 0;
1613             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1614
1615             for (i = start; i < end; i++) {
1616                 array.push(iterable[i]);
1617             }
1618
1619             return array;
1620         },
1621
1622         /**
1623          * Plucks the value of a property from each item in the Array. Example:
1624          *
1625          *     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1626          *
1627          * @param {Array/NodeList} array The Array of items to pluck the value from.
1628          * @param {String} propertyName The property name to pluck from each element.
1629          * @return {Array} The value from each item in the Array.
1630          */
1631         pluck: function(array, propertyName) {
1632             var ret = [],
1633                 i, ln, item;
1634
1635             for (i = 0, ln = array.length; i < ln; i++) {
1636                 item = array[i];
1637
1638                 ret.push(item[propertyName]);
1639             }
1640
1641             return ret;
1642         },
1643
1644         /**
1645          * Creates a new array with the results of calling a provided function on every element in this array.
1646          *
1647          * @param {Array} array
1648          * @param {Function} fn Callback function for each item
1649          * @param {Object} scope Callback function scope
1650          * @return {Array} results
1651          */
1652         map: function(array, fn, scope) {
1653             if (supportsMap) {
1654                 return array.map(fn, scope);
1655             }
1656
1657             var results = [],
1658                 i = 0,
1659                 len = array.length;
1660
1661             for (; i < len; i++) {
1662                 results[i] = fn.call(scope, array[i], i, array);
1663             }
1664
1665             return results;
1666         },
1667
1668         /**
1669          * Executes the specified function for each array element until the function returns a falsy value.
1670          * If such an item is found, the function will return false immediately.
1671          * Otherwise, it will return true.
1672          *
1673          * @param {Array} array
1674          * @param {Function} fn Callback function for each item
1675          * @param {Object} scope Callback function scope
1676          * @return {Boolean} True if no false value is returned by the callback function.
1677          */
1678         every: function(array, fn, scope) {
1679             if (!fn) {
1680                 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1681             }
1682             if (supportsEvery) {
1683                 return array.every(fn, scope);
1684             }
1685
1686             var i = 0,
1687                 ln = array.length;
1688
1689             for (; i < ln; ++i) {
1690                 if (!fn.call(scope, array[i], i, array)) {
1691                     return false;
1692                 }
1693             }
1694
1695             return true;
1696         },
1697
1698         /**
1699          * Executes the specified function for each array element until the function returns a truthy value.
1700          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1701          *
1702          * @param {Array} array
1703          * @param {Function} fn Callback function for each item
1704          * @param {Object} scope Callback function scope
1705          * @return {Boolean} True if the callback function returns a truthy value.
1706          */
1707         some: function(array, fn, scope) {
1708             if (!fn) {
1709                 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1710             }
1711             if (supportsSome) {
1712                 return array.some(fn, scope);
1713             }
1714
1715             var i = 0,
1716                 ln = array.length;
1717
1718             for (; i < ln; ++i) {
1719                 if (fn.call(scope, array[i], i, array)) {
1720                     return true;
1721                 }
1722             }
1723
1724             return false;
1725         },
1726
1727         /**
1728          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1729          *
1730          * See {@link Ext.Array#filter}
1731          *
1732          * @param {Array} array
1733          * @return {Array} results
1734          */
1735         clean: function(array) {
1736             var results = [],
1737                 i = 0,
1738                 ln = array.length,
1739                 item;
1740
1741             for (; i < ln; i++) {
1742                 item = array[i];
1743
1744                 if (!Ext.isEmpty(item)) {
1745                     results.push(item);
1746                 }
1747             }
1748
1749             return results;
1750         },
1751
1752         /**
1753          * Returns a new array with unique items
1754          *
1755          * @param {Array} array
1756          * @return {Array} results
1757          */
1758         unique: function(array) {
1759             var clone = [],
1760                 i = 0,
1761                 ln = array.length,
1762                 item;
1763
1764             for (; i < ln; i++) {
1765                 item = array[i];
1766
1767                 if (ExtArray.indexOf(clone, item) === -1) {
1768                     clone.push(item);
1769                 }
1770             }
1771
1772             return clone;
1773         },
1774
1775         /**
1776          * Creates a new array with all of the elements of this array for which
1777          * the provided filtering function returns true.
1778          *
1779          * @param {Array} array
1780          * @param {Function} fn Callback function for each item
1781          * @param {Object} scope Callback function scope
1782          * @return {Array} results
1783          */
1784         filter: function(array, fn, scope) {
1785             if (supportsFilter) {
1786                 return array.filter(fn, scope);
1787             }
1788
1789             var results = [],
1790                 i = 0,
1791                 ln = array.length;
1792
1793             for (; i < ln; i++) {
1794                 if (fn.call(scope, array[i], i, array)) {
1795                     results.push(array[i]);
1796                 }
1797             }
1798
1799             return results;
1800         },
1801
1802         /**
1803          * Converts a value to an array if it's not already an array; returns:
1804          *
1805          * - An empty array if given value is `undefined` or `null`
1806          * - Itself if given value is already an array
1807          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1808          * - An array with one item which is the given value, otherwise
1809          *
1810          * @param {Object} value The value to convert to an array if it's not already is an array
1811          * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
1812          * defaults to false
1813          * @return {Array} array
1814          */
1815         from: function(value, newReference) {
1816             if (value === undefined || value === null) {
1817                 return [];
1818             }
1819
1820             if (Ext.isArray(value)) {
1821                 return (newReference) ? slice.call(value) : value;
1822             }
1823
1824             if (value && value.length !== undefined && typeof value !== 'string') {
1825                 return Ext.toArray(value);
1826             }
1827
1828             return [value];
1829         },
1830
1831         /**
1832          * Removes the specified item from the array if it exists
1833          *
1834          * @param {Array} array The array
1835          * @param {Object} item The item to remove
1836          * @return {Array} The passed array itself
1837          */
1838         remove: function(array, item) {
1839             var index = ExtArray.indexOf(array, item);
1840
1841             if (index !== -1) {
1842                 erase(array, index, 1);
1843             }
1844
1845             return array;
1846         },
1847
1848         /**
1849          * Push an item into the array only if the array doesn't contain it yet
1850          *
1851          * @param {Array} array The array
1852          * @param {Object} item The item to include
1853          */
1854         include: function(array, item) {
1855             if (!ExtArray.contains(array, item)) {
1856                 array.push(item);
1857             }
1858         },
1859
1860         /**
1861          * Clone a flat array without referencing the previous one. Note that this is different
1862          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1863          * for Array.prototype.slice.call(array)
1864          *
1865          * @param {Array} array The array
1866          * @return {Array} The clone array
1867          */
1868         clone: function(array) {
1869             return slice.call(array);
1870         },
1871
1872         /**
1873          * Merge multiple arrays into one with unique items.
1874          *
1875          * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
1876          *
1877          * @param {Array} array1
1878          * @param {Array} array2
1879          * @param {Array} etc
1880          * @return {Array} merged
1881          */
1882         merge: function() {
1883             var args = slice.call(arguments),
1884                 array = [],
1885                 i, ln;
1886
1887             for (i = 0, ln = args.length; i < ln; i++) {
1888                 array = array.concat(args[i]);
1889             }
1890
1891             return ExtArray.unique(array);
1892         },
1893
1894         /**
1895          * Merge multiple arrays into one with unique items that exist in all of the arrays.
1896          *
1897          * @param {Array} array1
1898          * @param {Array} array2
1899          * @param {Array} etc
1900          * @return {Array} intersect
1901          */
1902         intersect: function() {
1903             var intersect = [],
1904                 arrays = slice.call(arguments),
1905                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1906
1907             if (!arrays.length) {
1908                 return intersect;
1909             }
1910
1911             // Find the smallest array
1912             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1913                 if (!minArray || array.length < minArray.length) {
1914                     minArray = array;
1915                     x = i;
1916                 }
1917             }
1918
1919             minArray = ExtArray.unique(minArray);
1920             erase(arrays, x, 1);
1921
1922             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1923             // an item in the small array, we're likely to find it before reaching the end
1924             // of the inner loop and can terminate the search early.
1925             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1926                 var count = 0;
1927
1928                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1929                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1930                         if (x === y) {
1931                             count++;
1932                             break;
1933                         }
1934                     }
1935                 }
1936
1937                 if (count === arraysLn) {
1938                     intersect.push(x);
1939                 }
1940             }
1941
1942             return intersect;
1943         },
1944
1945         /**
1946          * Perform a set difference A-B by subtracting all items in array B from array A.
1947          *
1948          * @param {Array} arrayA
1949          * @param {Array} arrayB
1950          * @return {Array} difference
1951          */
1952         difference: function(arrayA, arrayB) {
1953             var clone = slice.call(arrayA),
1954                 ln = clone.length,
1955                 i, j, lnB;
1956
1957             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1958                 for (j = 0; j < ln; j++) {
1959                     if (clone[j] === arrayB[i]) {
1960                         erase(clone, j, 1);
1961                         j--;
1962                         ln--;
1963                     }
1964                 }
1965             }
1966
1967             return clone;
1968         },
1969
1970         /**
1971          * Returns a shallow copy of a part of an array. This is equivalent to the native
1972          * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
1973          * is "arguments" since the arguments object does not supply a slice method but can
1974          * be the context object to Array.prototype.slice.
1975          *
1976          * @param {Array} array The array (or arguments object).
1977          * @param {Number} begin The index at which to begin. Negative values are offsets from
1978          * the end of the array.
1979          * @param {Number} end The index at which to end. The copied items do not include
1980          * end. Negative values are offsets from the end of the array. If end is omitted,
1981          * all items up to the end of the array are copied.
1982          * @return {Array} The copied piece of the array.
1983          */
1984         // Note: IE6 will return [] on slice.call(x, undefined).
1985         slice: ([1,2].slice(1, undefined).length ?
1986             function (array, begin, end) {
1987                 return slice.call(array, begin, end);
1988             } :
1989             // at least IE6 uses arguments.length for variadic signature
1990             function (array, begin, end) {
1991                 // After tested for IE 6, the one below is of the best performance
1992                 // see http://jsperf.com/slice-fix
1993                 if (typeof begin === 'undefined') {
1994                     return slice.call(array);
1995                 }
1996                 if (typeof end === 'undefined') {
1997                     return slice.call(array, begin);
1998                 }
1999                 return slice.call(array, begin, end);
2000             }
2001         ),
2002
2003         /**
2004          * Sorts the elements of an Array.
2005          * By default, this method sorts the elements alphabetically and ascending.
2006          *
2007          * @param {Array} array The array to sort.
2008          * @param {Function} sortFn (optional) The comparison function.
2009          * @return {Array} The sorted array.
2010          */
2011         sort: function(array, sortFn) {
2012             if (supportsSort) {
2013                 if (sortFn) {
2014                     return array.sort(sortFn);
2015                 } else {
2016                     return array.sort();
2017                 }
2018             }
2019
2020             var length = array.length,
2021                 i = 0,
2022                 comparison,
2023                 j, min, tmp;
2024
2025             for (; i < length; i++) {
2026                 min = i;
2027                 for (j = i + 1; j < length; j++) {
2028                     if (sortFn) {
2029                         comparison = sortFn(array[j], array[min]);
2030                         if (comparison < 0) {
2031                             min = j;
2032                         }
2033                     } else if (array[j] < array[min]) {
2034                         min = j;
2035                     }
2036                 }
2037                 if (min !== i) {
2038                     tmp = array[i];
2039                     array[i] = array[min];
2040                     array[min] = tmp;
2041                 }
2042             }
2043
2044             return array;
2045         },
2046
2047         /**
2048          * Recursively flattens into 1-d Array. Injects Arrays inline.
2049          *
2050          * @param {Array} array The array to flatten
2051          * @return {Array} The 1-d array.
2052          */
2053         flatten: function(array) {
2054             var worker = [];
2055
2056             function rFlatten(a) {
2057                 var i, ln, v;
2058
2059                 for (i = 0, ln = a.length; i < ln; i++) {
2060                     v = a[i];
2061
2062                     if (Ext.isArray(v)) {
2063                         rFlatten(v);
2064                     } else {
2065                         worker.push(v);
2066                     }
2067                 }
2068
2069                 return worker;
2070             }
2071
2072             return rFlatten(array);
2073         },
2074
2075         /**
2076          * Returns the minimum value in the Array.
2077          *
2078          * @param {Array/NodeList} array The Array from which to select the minimum value.
2079          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
2080          * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
2081          * @return {Object} minValue The minimum value
2082          */
2083         min: function(array, comparisonFn) {
2084             var min = array[0],
2085                 i, ln, item;
2086
2087             for (i = 0, ln = array.length; i < ln; i++) {
2088                 item = array[i];
2089
2090                 if (comparisonFn) {
2091                     if (comparisonFn(min, item) === 1) {
2092                         min = item;
2093                     }
2094                 }
2095                 else {
2096                     if (item < min) {
2097                         min = item;
2098                     }
2099                 }
2100             }
2101
2102             return min;
2103         },
2104
2105         /**
2106          * Returns the maximum value in the Array.
2107          *
2108          * @param {Array/NodeList} array The Array from which to select the maximum value.
2109          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
2110          * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
2111          * @return {Object} maxValue The maximum value
2112          */
2113         max: function(array, comparisonFn) {
2114             var max = array[0],
2115                 i, ln, item;
2116
2117             for (i = 0, ln = array.length; i < ln; i++) {
2118                 item = array[i];
2119
2120                 if (comparisonFn) {
2121                     if (comparisonFn(max, item) === -1) {
2122                         max = item;
2123                     }
2124                 }
2125                 else {
2126                     if (item > max) {
2127                         max = item;
2128                     }
2129                 }
2130             }
2131
2132             return max;
2133         },
2134
2135         /**
2136          * Calculates the mean of all items in the array.
2137          *
2138          * @param {Array} array The Array to calculate the mean value of.
2139          * @return {Number} The mean.
2140          */
2141         mean: function(array) {
2142             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
2143         },
2144
2145         /**
2146          * Calculates the sum of all items in the given array.
2147          *
2148          * @param {Array} array The Array to calculate the sum value of.
2149          * @return {Number} The sum.
2150          */
2151         sum: function(array) {
2152             var sum = 0,
2153                 i, ln, item;
2154
2155             for (i = 0,ln = array.length; i < ln; i++) {
2156                 item = array[i];
2157
2158                 sum += item;
2159             }
2160
2161             return sum;
2162         },
2163
2164         _replaceSim: replaceSim, // for unit testing
2165         _spliceSim: spliceSim,
2166
2167         /**
2168          * Removes items from an array. This is functionally equivalent to the splice method
2169          * of Array, but works around bugs in IE8's splice method and does not copy the
2170          * removed elements in order to return them (because very often they are ignored).
2171          *
2172          * @param {Array} array The Array on which to replace.
2173          * @param {Number} index The index in the array at which to operate.
2174          * @param {Number} removeCount The number of items to remove at index.
2175          * @return {Array} The array passed.
2176          * @method
2177          */
2178         erase: erase,
2179
2180         /**
2181          * Inserts items in to an array.
2182          *
2183          * @param {Array} array The Array on which to replace.
2184          * @param {Number} index The index in the array at which to operate.
2185          * @param {Array} items The array of items to insert at index.
2186          * @return {Array} The array passed.
2187          */
2188         insert: function (array, index, items) {
2189             return replace(array, index, 0, items);
2190         },
2191
2192         /**
2193          * Replaces items in an array. This is functionally equivalent to the splice method
2194          * of Array, but works around bugs in IE8's splice method and is often more convenient
2195          * to call because it accepts an array of items to insert rather than use a variadic
2196          * argument list.
2197          *
2198          * @param {Array} array The Array on which to replace.
2199          * @param {Number} index The index in the array at which to operate.
2200          * @param {Number} removeCount The number of items to remove at index (can be 0).
2201          * @param {Array} insert (optional) An array of items to insert at index.
2202          * @return {Array} The array passed.
2203          * @method
2204          */
2205         replace: replace,
2206
2207         /**
2208          * Replaces items in an array. This is equivalent to the splice method of Array, but
2209          * works around bugs in IE8's splice method. The signature is exactly the same as the
2210          * splice method except that the array is the first argument. All arguments following
2211          * removeCount are inserted in the array at index.
2212          *
2213          * @param {Array} array The Array on which to replace.
2214          * @param {Number} index The index in the array at which to operate.
2215          * @param {Number} removeCount The number of items to remove at index (can be 0).
2216          * @return {Array} An array containing the removed items.
2217          * @method
2218          */
2219         splice: splice
2220     };
2221
2222     /**
2223      * @method
2224      * @member Ext
2225      * @alias Ext.Array#each
2226      */
2227     Ext.each = ExtArray.each;
2228
2229     /**
2230      * @method
2231      * @member Ext.Array
2232      * @alias Ext.Array#merge
2233      */
2234     ExtArray.union = ExtArray.merge;
2235
2236     /**
2237      * Old alias to {@link Ext.Array#min}
2238      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
2239      * @method
2240      * @member Ext
2241      * @alias Ext.Array#min
2242      */
2243     Ext.min = ExtArray.min;
2244
2245     /**
2246      * Old alias to {@link Ext.Array#max}
2247      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
2248      * @method
2249      * @member Ext
2250      * @alias Ext.Array#max
2251      */
2252     Ext.max = ExtArray.max;
2253
2254     /**
2255      * Old alias to {@link Ext.Array#sum}
2256      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
2257      * @method
2258      * @member Ext
2259      * @alias Ext.Array#sum
2260      */
2261     Ext.sum = ExtArray.sum;
2262
2263     /**
2264      * Old alias to {@link Ext.Array#mean}
2265      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
2266      * @method
2267      * @member Ext
2268      * @alias Ext.Array#mean
2269      */
2270     Ext.mean = ExtArray.mean;
2271
2272     /**
2273      * Old alias to {@link Ext.Array#flatten}
2274      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
2275      * @method
2276      * @member Ext
2277      * @alias Ext.Array#flatten
2278      */
2279     Ext.flatten = ExtArray.flatten;
2280
2281     /**
2282      * Old alias to {@link Ext.Array#clean}
2283      * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
2284      * @method
2285      * @member Ext
2286      * @alias Ext.Array#clean
2287      */
2288     Ext.clean = ExtArray.clean;
2289
2290     /**
2291      * Old alias to {@link Ext.Array#unique}
2292      * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
2293      * @method
2294      * @member Ext
2295      * @alias Ext.Array#unique
2296      */
2297     Ext.unique = ExtArray.unique;
2298
2299     /**
2300      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2301      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2302      * @method
2303      * @member Ext
2304      * @alias Ext.Array#pluck
2305      */
2306     Ext.pluck = ExtArray.pluck;
2307
2308     /**
2309      * @method
2310      * @member Ext
2311      * @alias Ext.Array#toArray
2312      */
2313     Ext.toArray = function() {
2314         return ExtArray.toArray.apply(ExtArray, arguments);
2315     };
2316 })();
2317
2318 /**
2319  * @class Ext.Function
2320  *
2321  * A collection of useful static methods to deal with function callbacks
2322  * @singleton
2323  */
2324 Ext.Function = {
2325
2326     /**
2327      * A very commonly used method throughout the framework. It acts as a wrapper around another method
2328      * which originally accepts 2 arguments for `name` and `value`.
2329      * The wrapped function then allows "flexible" value setting of either:
2330      *
2331      * - `name` and `value` as 2 arguments
2332      * - one single object argument with multiple key - value pairs
2333      *
2334      * For example:
2335      *
2336      *     var setValue = Ext.Function.flexSetter(function(name, value) {
2337      *         this[name] = value;
2338      *     });
2339      *
2340      *     // Afterwards
2341      *     // Setting a single name - value
2342      *     setValue('name1', 'value1');
2343      *
2344      *     // Settings multiple name - value pairs
2345      *     setValue({
2346      *         name1: 'value1',
2347      *         name2: 'value2',
2348      *         name3: 'value3'
2349      *     });
2350      *
2351      * @param {Function} setter
2352      * @returns {Function} flexSetter
2353      */
2354     flexSetter: function(fn) {
2355         return function(a, b) {
2356             var k, i;
2357
2358             if (a === null) {
2359                 return this;
2360             }
2361
2362             if (typeof a !== 'string') {
2363                 for (k in a) {
2364                     if (a.hasOwnProperty(k)) {
2365                         fn.call(this, k, a[k]);
2366                     }
2367                 }
2368
2369                 if (Ext.enumerables) {
2370                     for (i = Ext.enumerables.length; i--;) {
2371                         k = Ext.enumerables[i];
2372                         if (a.hasOwnProperty(k)) {
2373                             fn.call(this, k, a[k]);
2374                         }
2375                     }
2376                 }
2377             } else {
2378                 fn.call(this, a, b);
2379             }
2380
2381             return this;
2382         };
2383     },
2384
2385     /**
2386      * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
2387      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2388      *
2389      * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
2390      *
2391      * @param {Function} fn The function to delegate.
2392      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2393      * **If omitted, defaults to the browser window.**
2394      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2395      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2396      * if a number the args are inserted at the specified position
2397      * @return {Function} The new function
2398      */
2399     bind: function(fn, scope, args, appendArgs) {
2400         if (arguments.length === 2) {
2401             return function() {
2402                 return fn.apply(scope, arguments);
2403             }
2404         }
2405
2406         var method = fn,
2407             slice = Array.prototype.slice;
2408
2409         return function() {
2410             var callArgs = args || arguments;
2411
2412             if (appendArgs === true) {
2413                 callArgs = slice.call(arguments, 0);
2414                 callArgs = callArgs.concat(args);
2415             }
2416             else if (typeof appendArgs == 'number') {
2417                 callArgs = slice.call(arguments, 0); // copy arguments first
2418                 Ext.Array.insert(callArgs, appendArgs, args);
2419             }
2420
2421             return method.apply(scope || window, callArgs);
2422         };
2423     },
2424
2425     /**
2426      * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
2427      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2428      * This is especially useful when creating callbacks.
2429      *
2430      * For example:
2431      *
2432      *     var originalFunction = function(){
2433      *         alert(Ext.Array.from(arguments).join(' '));
2434      *     };
2435      *
2436      *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2437      *
2438      *     callback(); // alerts 'Hello World'
2439      *     callback('by Me'); // alerts 'Hello World by Me'
2440      *
2441      * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
2442      *
2443      * @param {Function} fn The original function
2444      * @param {Array} args The arguments to pass to new callback
2445      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2446      * @return {Function} The new callback function
2447      */
2448     pass: function(fn, args, scope) {
2449         if (args) {
2450             args = Ext.Array.from(args);
2451         }
2452
2453         return function() {
2454             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2455         };
2456     },
2457
2458     /**
2459      * Create an alias to the provided method property with name `methodName` of `object`.
2460      * Note that the execution scope will still be bound to the provided `object` itself.
2461      *
2462      * @param {Object/Function} object
2463      * @param {String} methodName
2464      * @return {Function} aliasFn
2465      */
2466     alias: function(object, methodName) {
2467         return function() {
2468             return object[methodName].apply(object, arguments);
2469         };
2470     },
2471
2472     /**
2473      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2474      * the original one is not called. The resulting function returns the results of the original function.
2475      * The passed function is called with the parameters of the original function. Example usage:
2476      *
2477      *     var sayHi = function(name){
2478      *         alert('Hi, ' + name);
2479      *     }
2480      *
2481      *     sayHi('Fred'); // alerts "Hi, Fred"
2482      *
2483      *     // create a new function that validates input without
2484      *     // directly modifying the original function:
2485      *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2486      *         return name == 'Brian';
2487      *     });
2488      *
2489      *     sayHiToFriend('Fred');  // no alert
2490      *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
2491      *
2492      * @param {Function} origFn The original function.
2493      * @param {Function} newFn The function to call before the original
2494      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2495      * **If omitted, defaults to the scope in which the original function is called or the browser window.**
2496      * @param {Object} returnValue (optional) The value to return if the passed function return false (defaults to null).
2497      * @return {Function} The new function
2498      */
2499     createInterceptor: function(origFn, newFn, scope, returnValue) {
2500         var method = origFn;
2501         if (!Ext.isFunction(newFn)) {
2502             return origFn;
2503         }
2504         else {
2505             return function() {
2506                 var me = this,
2507                     args = arguments;
2508                 newFn.target = me;
2509                 newFn.method = origFn;
2510                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2511             };
2512         }
2513     },
2514
2515     /**
2516      * Creates a delegate (callback) which, when called, executes after a specific delay.
2517      *
2518      * @param {Function} fn The function which will be called on a delay when the returned function is called.
2519      * Optionally, a replacement (or additional) argument list may be specified.
2520      * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2521      * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
2522      * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2523      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2524      * if a number the args are inserted at the specified position.
2525      * @return {Function} A function which, when called, executes the original function after the specified delay.
2526      */
2527     createDelayed: function(fn, delay, scope, args, appendArgs) {
2528         if (scope || args) {
2529             fn = Ext.Function.bind(fn, scope, args, appendArgs);
2530         }
2531         return function() {
2532             var me = this;
2533             setTimeout(function() {
2534                 fn.apply(me, arguments);
2535             }, delay);
2536         };
2537     },
2538
2539     /**
2540      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2541      *
2542      *     var sayHi = function(name){
2543      *         alert('Hi, ' + name);
2544      *     }
2545      *
2546      *     // executes immediately:
2547      *     sayHi('Fred');
2548      *
2549      *     // executes after 2 seconds:
2550      *     Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2551      *
2552      *     // this syntax is sometimes useful for deferring
2553      *     // execution of an anonymous function:
2554      *     Ext.Function.defer(function(){
2555      *         alert('Anonymous');
2556      *     }, 100);
2557      *
2558      * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
2559      *
2560      * @param {Function} fn The function to defer.
2561      * @param {Number} millis The number of milliseconds for the setTimeout call
2562      * (if less than or equal to 0 the function is executed immediately)
2563      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2564      * **If omitted, defaults to the browser window.**
2565      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2566      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2567      * if a number the args are inserted at the specified position
2568      * @return {Number} The timeout id that can be used with clearTimeout
2569      */
2570     defer: function(fn, millis, obj, args, appendArgs) {
2571         fn = Ext.Function.bind(fn, obj, args, appendArgs);
2572         if (millis > 0) {
2573             return setTimeout(fn, millis);
2574         }
2575         fn();
2576         return 0;
2577     },
2578
2579     /**
2580      * Create a combined function call sequence of the original function + the passed function.
2581      * The resulting function returns the results of the original function.
2582      * The passed function is called with the parameters of the original function. Example usage:
2583      *
2584      *     var sayHi = function(name){
2585      *         alert('Hi, ' + name);
2586      *     }
2587      *
2588      *     sayHi('Fred'); // alerts "Hi, Fred"
2589      *
2590      *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2591      *         alert('Bye, ' + name);
2592      *     });
2593      *
2594      *     sayGoodbye('Fred'); // both alerts show
2595      *
2596      * @param {Function} origFn The original function.
2597      * @param {Function} newFn The function to sequence
2598      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2599      * If omitted, defaults to the scope in which the original function is called or the browser window.
2600      * @return {Function} The new function
2601      */
2602     createSequence: function(origFn, newFn, scope) {
2603         if (!Ext.isFunction(newFn)) {
2604             return origFn;
2605         }
2606         else {
2607             return function() {
2608                 var retval = origFn.apply(this || window, arguments);
2609                 newFn.apply(scope || this || window, arguments);
2610                 return retval;
2611             };
2612         }
2613     },
2614
2615     /**
2616      * Creates a delegate function, optionally with a bound scope which, when called, buffers
2617      * the execution of the passed function for the configured number of milliseconds.
2618      * If called again within that period, the impending invocation will be canceled, and the
2619      * timeout period will begin again.
2620      *
2621      * @param {Function} fn The function to invoke on a buffered timer.
2622      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2623      * function.
2624      * @param {Object} scope (optional) The scope (`this` reference) in which
2625      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2626      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2627      * passed by the caller.
2628      * @return {Function} A function which invokes the passed function after buffering for the specified time.
2629      */
2630     createBuffered: function(fn, buffer, scope, args) {
2631         return function(){
2632             var timerId;
2633             return function() {
2634                 var me = this;
2635                 if (timerId) {
2636                     clearTimeout(timerId);
2637                     timerId = null;
2638                 }
2639                 timerId = setTimeout(function(){
2640                     fn.apply(scope || me, args || arguments);
2641                 }, buffer);
2642             };
2643         }();
2644     },
2645
2646     /**
2647      * Creates a throttled version of the passed function which, when called repeatedly and
2648      * rapidly, invokes the passed function only after a certain interval has elapsed since the
2649      * previous invocation.
2650      *
2651      * This is useful for wrapping functions which may be called repeatedly, such as
2652      * a handler of a mouse move event when the processing is expensive.
2653      *
2654      * @param {Function} fn The function to execute at a regular time interval.
2655      * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
2656      * @param {Object} scope (optional) The scope (`this` reference) in which
2657      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2658      * @returns {Function} A function which invokes the passed function at the specified interval.
2659      */
2660     createThrottled: function(fn, interval, scope) {
2661         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2662             fn.apply(scope || this, lastArgs);
2663             lastCallTime = new Date().getTime();
2664         };
2665
2666         return function() {
2667             elapsed = new Date().getTime() - lastCallTime;
2668             lastArgs = arguments;
2669
2670             clearTimeout(timer);
2671             if (!lastCallTime || (elapsed >= interval)) {
2672                 execute();
2673             } else {
2674                 timer = setTimeout(execute, interval - elapsed);
2675             }
2676         };
2677     },
2678
2679     /**
2680      * Adds behavior to an existing method that is executed before the
2681      * original behavior of the function.  For example:
2682      * 
2683      *     var soup = {
2684      *         contents: [],
2685      *         add: function(ingredient) {
2686      *             this.contents.push(ingredient);
2687      *         }
2688      *     };
2689      *     Ext.Function.interceptBefore(soup, "add", function(ingredient){
2690      *         if (!this.contents.length && ingredient !== "water") {
2691      *             // Always add water to start with
2692      *             this.contents.push("water");
2693      *         }
2694      *     });
2695      *     soup.add("onions");
2696      *     soup.add("salt");
2697      *     soup.contents; // will contain: water, onions, salt
2698      * 
2699      * @param {Object} object The target object
2700      * @param {String} methodName Name of the method to override
2701      * @param {Function} fn Function with the new behavior.  It will
2702      * be called with the same arguments as the original method.  The
2703      * return value of this function will be the return value of the
2704      * new method.
2705      * @return {Function} The new function just created.
2706      */
2707     interceptBefore: function(object, methodName, fn) {
2708         var method = object[methodName] || Ext.emptyFn;
2709
2710         return object[methodName] = function() {
2711             var ret = fn.apply(this, arguments);
2712             method.apply(this, arguments);
2713
2714             return ret;
2715         };
2716     },
2717
2718     /**
2719      * Adds behavior to an existing method that is executed after the
2720      * original behavior of the function.  For example:
2721      * 
2722      *     var soup = {
2723      *         contents: [],
2724      *         add: function(ingredient) {
2725      *             this.contents.push(ingredient);
2726      *         }
2727      *     };
2728      *     Ext.Function.interceptAfter(soup, "add", function(ingredient){
2729      *         // Always add a bit of extra salt
2730      *         this.contents.push("salt");
2731      *     });
2732      *     soup.add("water");
2733      *     soup.add("onions");
2734      *     soup.contents; // will contain: water, salt, onions, salt
2735      * 
2736      * @param {Object} object The target object
2737      * @param {String} methodName Name of the method to override
2738      * @param {Function} fn Function with the new behavior.  It will
2739      * be called with the same arguments as the original method.  The
2740      * return value of this function will be the return value of the
2741      * new method.
2742      * @return {Function} The new function just created.
2743      */
2744     interceptAfter: function(object, methodName, fn) {
2745         var method = object[methodName] || Ext.emptyFn;
2746
2747         return object[methodName] = function() {
2748             method.apply(this, arguments);
2749             return fn.apply(this, arguments);
2750         };
2751     }
2752 };
2753
2754 /**
2755  * @method
2756  * @member Ext
2757  * @alias Ext.Function#defer
2758  */
2759 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2760
2761 /**
2762  * @method
2763  * @member Ext
2764  * @alias Ext.Function#pass
2765  */
2766 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2767
2768 /**
2769  * @method
2770  * @member Ext
2771  * @alias Ext.Function#bind
2772  */
2773 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2774
2775 /**
2776  * @author Jacky Nguyen <jacky@sencha.com>
2777  * @docauthor Jacky Nguyen <jacky@sencha.com>
2778  * @class Ext.Object
2779  *
2780  * A collection of useful static methods to deal with objects.
2781  *
2782  * @singleton
2783  */
2784
2785 (function() {
2786
2787 var ExtObject = Ext.Object = {
2788
2789     /**
2790      * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
2791      * query strings. For example:
2792      *
2793      *     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2794      *
2795      *     // objects then equals:
2796      *     [
2797      *         { name: 'hobbies', value: 'reading' },
2798      *         { name: 'hobbies', value: 'cooking' },
2799      *         { name: 'hobbies', value: 'swimming' },
2800      *     ];
2801      *
2802      *     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2803      *         day: 3,
2804      *         month: 8,
2805      *         year: 1987,
2806      *         extra: {
2807      *             hour: 4
2808      *             minute: 30
2809      *         }
2810      *     }, true); // Recursive
2811      *
2812      *     // objects then equals:
2813      *     [
2814      *         { name: 'dateOfBirth[day]', value: 3 },
2815      *         { name: 'dateOfBirth[month]', value: 8 },
2816      *         { name: 'dateOfBirth[year]', value: 1987 },
2817      *         { name: 'dateOfBirth[extra][hour]', value: 4 },
2818      *         { name: 'dateOfBirth[extra][minute]', value: 30 },
2819      *     ];
2820      *
2821      * @param {String} name
2822      * @param {Object/Array} value
2823      * @param {Boolean} [recursive=false] True to traverse object recursively
2824      * @return {Array}
2825      */
2826     toQueryObjects: function(name, value, recursive) {
2827         var self = ExtObject.toQueryObjects,
2828             objects = [],
2829             i, ln;
2830
2831         if (Ext.isArray(value)) {
2832             for (i = 0, ln = value.length; i < ln; i++) {
2833                 if (recursive) {
2834                     objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2835                 }
2836                 else {
2837                     objects.push({
2838                         name: name,
2839                         value: value[i]
2840                     });
2841                 }
2842             }
2843         }
2844         else if (Ext.isObject(value)) {
2845             for (i in value) {
2846                 if (value.hasOwnProperty(i)) {
2847                     if (recursive) {
2848                         objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2849                     }
2850                     else {
2851                         objects.push({
2852                             name: name,
2853                             value: value[i]
2854                         });
2855                     }
2856                 }
2857             }
2858         }
2859         else {
2860             objects.push({
2861                 name: name,
2862                 value: value
2863             });
2864         }
2865
2866         return objects;
2867     },
2868
2869     /**
2870      * Takes an object and converts it to an encoded query string.
2871      *
2872      * Non-recursive:
2873      *
2874      *     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2875      *     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2876      *     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2877      *     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2878      *     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2879      *
2880      * Recursive:
2881      *
2882      *     Ext.Object.toQueryString({
2883      *         username: 'Jacky',
2884      *         dateOfBirth: {
2885      *             day: 1,
2886      *             month: 2,
2887      *             year: 1911
2888      *         },
2889      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2890      *     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2891      *     // username=Jacky
2892      *     //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2893      *     //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2894      *
2895      * @param {Object} object The object to encode
2896      * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
2897      * (PHP / Ruby on Rails servers and similar).
2898      * @return {String} queryString
2899      */
2900     toQueryString: function(object, recursive) {
2901         var paramObjects = [],
2902             params = [],
2903             i, j, ln, paramObject, value;
2904
2905         for (i in object) {
2906             if (object.hasOwnProperty(i)) {
2907                 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2908             }
2909         }
2910
2911         for (j = 0, ln = paramObjects.length; j < ln; j++) {
2912             paramObject = paramObjects[j];
2913             value = paramObject.value;
2914
2915             if (Ext.isEmpty(value)) {
2916                 value = '';
2917             }
2918             else if (Ext.isDate(value)) {
2919                 value = Ext.Date.toString(value);
2920             }
2921
2922             params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2923         }
2924
2925         return params.join('&');
2926     },
2927
2928     /**
2929      * Converts a query string back into an object.
2930      *
2931      * Non-recursive:
2932      *
2933      *     Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2934      *     Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2935      *     Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2936      *     Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2937      *
2938      * Recursive:
2939      *
2940      *       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);
2941      *     // returns
2942      *     {
2943      *         username: 'Jacky',
2944      *         dateOfBirth: {
2945      *             day: '1',
2946      *             month: '2',
2947      *             year: '1911'
2948      *         },
2949      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2950      *     }
2951      *
2952      * @param {String} queryString The query string to decode
2953      * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
2954      * PHP / Ruby on Rails servers and similar.
2955      * @return {Object}
2956      */
2957     fromQueryString: function(queryString, recursive) {
2958         var parts = queryString.replace(/^\?/, '').split('&'),
2959             object = {},
2960             temp, components, name, value, i, ln,
2961             part, j, subLn, matchedKeys, matchedName,
2962             keys, key, nextKey;
2963
2964         for (i = 0, ln = parts.length; i < ln; i++) {
2965             part = parts[i];
2966
2967             if (part.length > 0) {
2968                 components = part.split('=');
2969                 name = decodeURIComponent(components[0]);
2970                 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2971
2972                 if (!recursive) {
2973                     if (object.hasOwnProperty(name)) {
2974                         if (!Ext.isArray(object[name])) {
2975                             object[name] = [object[name]];
2976                         }
2977
2978                         object[name].push(value);
2979                     }
2980                     else {
2981                         object[name] = value;
2982                     }
2983                 }
2984                 else {
2985                     matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2986                     matchedName = name.match(/^([^\[]+)/);
2987
2988                     if (!matchedName) {
2989                         Ext.Error.raise({
2990                             sourceClass: "Ext.Object",
2991                             sourceMethod: "fromQueryString",
2992                             queryString: queryString,
2993                             recursive: recursive,
2994                             msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2995                         });
2996                     }
2997
2998                     name = matchedName[0];
2999                     keys = [];
3000
3001                     if (matchedKeys === null) {
3002                         object[name] = value;
3003                         continue;
3004                     }
3005
3006                     for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
3007                         key = matchedKeys[j];
3008                         key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
3009                         keys.push(key);
3010                     }
3011
3012                     keys.unshift(name);
3013
3014                     temp = object;
3015
3016                     for (j = 0, subLn = keys.length; j < subLn; j++) {
3017                         key = keys[j];
3018
3019                         if (j === subLn - 1) {
3020                             if (Ext.isArray(temp) && key === '') {
3021                                 temp.push(value);
3022                             }
3023                             else {
3024                                 temp[key] = value;
3025                             }
3026                         }
3027                         else {
3028                             if (temp[key] === undefined || typeof temp[key] === 'string') {
3029                                 nextKey = keys[j+1];
3030
3031                                 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
3032                             }
3033
3034                             temp = temp[key];
3035                         }
3036                     }
3037                 }
3038             }
3039         }
3040
3041         return object;
3042     },
3043
3044     /**
3045      * Iterates through an object and invokes the given callback function for each iteration.
3046      * The iteration can be stopped by returning `false` in the callback function. For example:
3047      *
3048      *     var person = {
3049      *         name: 'Jacky'
3050      *         hairColor: 'black'
3051      *         loves: ['food', 'sleeping', 'wife']
3052      *     };
3053      *
3054      *     Ext.Object.each(person, function(key, value, myself) {
3055      *         console.log(key + ":" + value);
3056      *
3057      *         if (key === 'hairColor') {
3058      *             return false; // stop the iteration
3059      *         }
3060      *     });
3061      *
3062      * @param {Object} object The object to iterate
3063      * @param {Function} fn The callback function.
3064      * @param {String} fn.key
3065      * @param {Object} fn.value
3066      * @param {Object} fn.object The object itself
3067      * @param {Object} [scope] The execution scope (`this`) of the callback function
3068      */
3069     each: function(object, fn, scope) {
3070         for (var property in object) {
3071             if (object.hasOwnProperty(property)) {
3072                 if (fn.call(scope || object, property, object[property], object) === false) {
3073                     return;
3074                 }
3075             }
3076         }
3077     },
3078
3079     /**
3080      * Merges any number of objects recursively without referencing them or their children.
3081      *
3082      *     var extjs = {
3083      *         companyName: 'Ext JS',
3084      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
3085      *         isSuperCool: true
3086      *         office: {
3087      *             size: 2000,
3088      *             location: 'Palo Alto',
3089      *             isFun: true
3090      *         }
3091      *     };
3092      *
3093      *     var newStuff = {
3094      *         companyName: 'Sencha Inc.',
3095      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3096      *         office: {
3097      *             size: 40000,
3098      *             location: 'Redwood City'
3099      *         }
3100      *     };
3101      *
3102      *     var sencha = Ext.Object.merge(extjs, newStuff);
3103      *
3104      *     // extjs and sencha then equals to
3105      *     {
3106      *         companyName: 'Sencha Inc.',
3107      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3108      *         isSuperCool: true
3109      *         office: {
3110      *             size: 30000,
3111      *             location: 'Redwood City'
3112      *             isFun: true
3113      *         }
3114      *     }
3115      *
3116      * @param {Object...} object Any number of objects to merge.
3117      * @return {Object} merged The object that is created as a result of merging all the objects passed in.
3118      */
3119     merge: function(source, key, value) {
3120         if (typeof key === 'string') {
3121             if (value && value.constructor === Object) {
3122                 if (source[key] && source[key].constructor === Object) {
3123                     ExtObject.merge(source[key], value);
3124                 }
3125                 else {
3126                     source[key] = Ext.clone(value);
3127                 }
3128             }
3129             else {
3130                 source[key] = value;
3131             }
3132
3133             return source;
3134         }
3135
3136         var i = 1,
3137             ln = arguments.length,
3138             object, property;
3139
3140         for (; i < ln; i++) {
3141             object = arguments[i];
3142
3143             for (property in object) {
3144                 if (object.hasOwnProperty(property)) {
3145                     ExtObject.merge(source, property, object[property]);
3146                 }
3147             }
3148         }
3149
3150         return source;
3151     },
3152
3153     /**
3154      * Returns the first matching key corresponding to the given value.
3155      * If no matching value is found, null is returned.
3156      *
3157      *     var person = {
3158      *         name: 'Jacky',
3159      *         loves: 'food'
3160      *     };
3161      *
3162      *     alert(Ext.Object.getKey(person, 'food')); // alerts 'loves'
3163      *
3164      * @param {Object} object
3165      * @param {Object} value The value to find
3166      */
3167     getKey: function(object, value) {
3168         for (var property in object) {
3169             if (object.hasOwnProperty(property) && object[property] === value) {
3170                 return property;
3171             }
3172         }
3173
3174         return null;
3175     },
3176
3177     /**
3178      * Gets all values of the given object as an array.
3179      *
3180      *     var values = Ext.Object.getValues({
3181      *         name: 'Jacky',
3182      *         loves: 'food'
3183      *     }); // ['Jacky', 'food']
3184      *
3185      * @param {Object} object
3186      * @return {Array} An array of values from the object
3187      */
3188     getValues: function(object) {
3189         var values = [],
3190             property;
3191
3192         for (property in object) {
3193             if (object.hasOwnProperty(property)) {
3194                 values.push(object[property]);
3195             }
3196         }
3197
3198         return values;
3199     },
3200
3201     /**
3202      * Gets all keys of the given object as an array.
3203      *
3204      *     var values = Ext.Object.getKeys({
3205      *         name: 'Jacky',
3206      *         loves: 'food'
3207      *     }); // ['name', 'loves']
3208      *
3209      * @param {Object} object
3210      * @return {String[]} An array of keys from the object
3211      * @method
3212      */
3213     getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
3214         var keys = [],
3215             property;
3216
3217         for (property in object) {
3218             if (object.hasOwnProperty(property)) {
3219                 keys.push(property);
3220             }
3221         }
3222
3223         return keys;
3224     },
3225
3226     /**
3227      * Gets the total number of this object's own properties
3228      *
3229      *     var size = Ext.Object.getSize({
3230      *         name: 'Jacky',
3231      *         loves: 'food'
3232      *     }); // size equals 2
3233      *
3234      * @param {Object} object
3235      * @return {Number} size
3236      */
3237     getSize: function(object) {
3238         var size = 0,
3239             property;
3240
3241         for (property in object) {
3242             if (object.hasOwnProperty(property)) {
3243                 size++;
3244             }
3245         }
3246
3247         return size;
3248     }
3249 };
3250
3251
3252 /**
3253  * A convenient alias method for {@link Ext.Object#merge}.
3254  *
3255  * @member Ext
3256  * @method merge
3257  * @alias Ext.Object#merge
3258  */
3259 Ext.merge = Ext.Object.merge;
3260
3261 /**
3262  * Alias for {@link Ext.Object#toQueryString}.
3263  *
3264  * @member Ext
3265  * @method urlEncode
3266  * @alias Ext.Object#toQueryString
3267  * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
3268  */
3269 Ext.urlEncode = function() {
3270     var args = Ext.Array.from(arguments),
3271         prefix = '';
3272
3273     // Support for the old `pre` argument
3274     if ((typeof args[1] === 'string')) {
3275         prefix = args[1] + '&';
3276         args[1] = false;
3277     }
3278
3279     return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
3280 };
3281
3282 /**
3283  * Alias for {@link Ext.Object#fromQueryString}.
3284  *
3285  * @member Ext
3286  * @method urlDecode
3287  * @alias Ext.Object#fromQueryString
3288  * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
3289  */
3290 Ext.urlDecode = function() {
3291     return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
3292 };
3293
3294 })();
3295
3296 /**
3297  * @class Ext.Date
3298  * A set of useful static methods to deal with date
3299  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
3300  * this object for convenience
3301  *
3302  * The date parsing and formatting syntax contains a subset of
3303  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
3304  * supported will provide results equivalent to their PHP versions.
3305  *
3306  * The following is a list of all currently supported formats:
3307  * <pre class="">
3308 Format  Description                                                               Example returned values
3309 ------  -----------------------------------------------------------------------   -----------------------
3310   d     Day of the month, 2 digits with leading zeros                             01 to 31
3311   D     A short textual representation of the day of the week                     Mon to Sun
3312   j     Day of the month without leading zeros                                    1 to 31
3313   l     A full textual representation of the day of the week                      Sunday to Saturday
3314   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
3315   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
3316   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
3317   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
3318   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
3319   F     A full textual representation of a month, such as January or March        January to December
3320   m     Numeric representation of a month, with leading zeros                     01 to 12
3321   M     A short textual representation of a month                                 Jan to Dec
3322   n     Numeric representation of a month, without leading zeros                  1 to 12
3323   t     Number of days in the given month                                         28 to 31
3324   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
3325   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
3326         belongs to the previous or next year, that year is used instead)
3327   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
3328   y     A two digit representation of a year                                      Examples: 99 or 03
3329   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
3330   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
3331   g     12-hour format of an hour without leading zeros                           1 to 12
3332   G     24-hour format of an hour without leading zeros                           0 to 23
3333   h     12-hour format of an hour with leading zeros                              01 to 12
3334   H     24-hour format of an hour with leading zeros                              00 to 23
3335   i     Minutes, with leading zeros                                               00 to 59
3336   s     Seconds, with leading zeros                                               00 to 59
3337   u     Decimal fraction of a second                                              Examples:
3338         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
3339                                                                                   100 (i.e. 0.100s) or
3340                                                                                   999 (i.e. 0.999s) or
3341                                                                                   999876543210 (i.e. 0.999876543210s)
3342   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
3343   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
3344   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
3345   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
3346   c     ISO 8601 date
3347         Notes:                                                                    Examples:
3348         1) If unspecified, the month / day defaults to the current month / day,   1991 or
3349            the time defaults to midnight, while the timezone defaults to the      1992-10 or
3350            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
3351            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
3352            are optional.                                                          1995-07-18T17:21:28-02:00 or
3353         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
3354            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
3355            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
3356         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
3357         date-time granularity which are supported, or see                         2000-02-13T21:25:33
3358         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
3359   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
3360   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
3361                                                                                   \/Date(1238606590509+0800)\/
3362 </pre>
3363  *
3364  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
3365  * <pre><code>
3366 // Sample date:
3367 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3368
3369 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3370 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
3371 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
3372 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
3373 </code></pre>
3374  *
3375  * Here are some standard date/time patterns that you might find helpful.  They
3376  * are not part of the source of Ext.Date, but to use them you can simply copy this
3377  * block of code into any script that is included after Ext.Date and they will also become
3378  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
3379  * <pre><code>
3380 Ext.Date.patterns = {
3381     ISO8601Long:"Y-m-d H:i:s",
3382     ISO8601Short:"Y-m-d",
3383     ShortDate: "n/j/Y",
3384     LongDate: "l, F d, Y",
3385     FullDateTime: "l, F d, Y g:i:s A",
3386     MonthDay: "F d",
3387     ShortTime: "g:i A",
3388     LongTime: "g:i:s A",
3389     SortableDateTime: "Y-m-d\\TH:i:s",
3390     UniversalSortableDateTime: "Y-m-d H:i:sO",
3391     YearMonth: "F, Y"
3392 };
3393 </code></pre>
3394  *
3395  * Example usage:
3396  * <pre><code>
3397 var dt = new Date();
3398 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3399 </code></pre>
3400  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3401  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3402  * @singleton
3403  */
3404
3405 /*
3406  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3407  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3408  * They generate precompiled functions from format patterns instead of parsing and
3409  * processing each pattern every time a date is formatted. These functions are available
3410  * on every Date object.
3411  */
3412
3413 (function() {
3414
3415 // create private copy of Ext's Ext.util.Format.format() method
3416 // - to remove unnecessary dependency
3417 // - to resolve namespace conflict with MS-Ajax's implementation
3418 function xf(format) {
3419     var args = Array.prototype.slice.call(arguments, 1);
3420     return format.replace(/\{(\d+)\}/g, function(m, i) {
3421         return args[i];
3422     });
3423 }
3424
3425 Ext.Date = {
3426     /**
3427      * Returns the current timestamp
3428      * @return {Date} The current timestamp
3429      * @method
3430      */
3431     now: Date.now || function() {
3432         return +new Date();
3433     },
3434
3435     /**
3436      * @private
3437      * Private for now
3438      */
3439     toString: function(date) {
3440         var pad = Ext.String.leftPad;
3441
3442         return date.getFullYear() + "-"
3443             + pad(date.getMonth() + 1, 2, '0') + "-"
3444             + pad(date.getDate(), 2, '0') + "T"
3445             + pad(date.getHours(), 2, '0') + ":"
3446             + pad(date.getMinutes(), 2, '0') + ":"
3447             + pad(date.getSeconds(), 2, '0');
3448     },
3449
3450     /**
3451      * Returns the number of milliseconds between two dates
3452      * @param {Date} dateA The first date
3453      * @param {Date} dateB (optional) The second date, defaults to now
3454      * @return {Number} The difference in milliseconds
3455      */
3456     getElapsed: function(dateA, dateB) {
3457         return Math.abs(dateA - (dateB || new Date()));
3458     },
3459
3460     /**
3461      * Global flag which determines if strict date parsing should be used.
3462      * Strict date parsing will not roll-over invalid dates, which is the
3463      * default behaviour of javascript Date objects.
3464      * (see {@link #parse} for more information)
3465      * Defaults to <tt>false</tt>.
3466      * @type Boolean
3467     */
3468     useStrict: false,
3469
3470     // private
3471     formatCodeToRegex: function(character, currentGroup) {
3472         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3473         var p = utilDate.parseCodes[character];
3474
3475         if (p) {
3476           p = typeof p == 'function'? p() : p;
3477           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3478         }
3479
3480         return p ? Ext.applyIf({
3481           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3482         }, p) : {
3483             g: 0,
3484             c: null,
3485             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3486         };
3487     },
3488
3489     /**
3490      * <p>An object hash in which each property is a date parsing function. The property name is the
3491      * format string which that function parses.</p>
3492      * <p>This object is automatically populated with date parsing functions as
3493      * date formats are requested for Ext standard formatting strings.</p>
3494      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3495      * may be used as a format string to {@link #parse}.<p>
3496      * <p>Example:</p><pre><code>
3497 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3498 </code></pre>
3499      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3500      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3501      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3502      * (i.e. prevent javascript Date "rollover") (The default must be false).
3503      * Invalid date strings should return null when parsed.</div></li>
3504      * </ul></div></p>
3505      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3506      * formatting function must be placed into the {@link #formatFunctions} property.
3507      * @property parseFunctions
3508      * @type Object
3509      */
3510     parseFunctions: {
3511         "MS": function(input, strict) {
3512             // note: the timezone offset is ignored since the MS Ajax server sends
3513             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3514             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3515             var r = (input || '').match(re);
3516             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3517         }
3518     },
3519     parseRegexes: [],
3520
3521     /**
3522      * <p>An object hash in which each property is a date formatting function. The property name is the
3523      * format string which corresponds to the produced formatted date string.</p>
3524      * <p>This object is automatically populated with date formatting functions as
3525      * date formats are requested for Ext standard formatting strings.</p>
3526      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3527      * may be used as a format string to {@link #format}. Example:</p><pre><code>
3528 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3529 </code></pre>
3530      * <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>
3531      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3532      * </ul></div></p>
3533      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3534      * parsing function must be placed into the {@link #parseFunctions} property.
3535      * @property formatFunctions
3536      * @type Object
3537      */
3538     formatFunctions: {
3539         "MS": function() {
3540             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3541             return '\\/Date(' + this.getTime() + ')\\/';
3542         }
3543     },
3544
3545     y2kYear : 50,
3546
3547     /**
3548      * Date interval constant
3549      * @type String
3550      */
3551     MILLI : "ms",
3552
3553     /**
3554      * Date interval constant
3555      * @type String
3556      */
3557     SECOND : "s",
3558
3559     /**
3560      * Date interval constant
3561      * @type String
3562      */
3563     MINUTE : "mi",
3564
3565     /** Date interval constant
3566      * @type String
3567      */
3568     HOUR : "h",
3569
3570     /**
3571      * Date interval constant
3572      * @type String
3573      */
3574     DAY : "d",
3575
3576     /**
3577      * Date interval constant
3578      * @type String
3579      */
3580     MONTH : "mo",
3581
3582     /**
3583      * Date interval constant
3584      * @type String
3585      */
3586     YEAR : "y",
3587
3588     /**
3589      * <p>An object hash containing default date values used during date parsing.</p>
3590      * <p>The following properties are available:<div class="mdetail-params"><ul>
3591      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3592      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3593      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3594      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3595      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3596      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3597      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3598      * </ul></div></p>
3599      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3600      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3601      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3602      * It is the responsiblity of the developer to account for this.</b></p>
3603      * Example Usage:
3604      * <pre><code>
3605 // set default day value to the first day of the month
3606 Ext.Date.defaults.d = 1;
3607
3608 // parse a February date string containing only year and month values.
3609 // setting the default day value to 1 prevents weird date rollover issues
3610 // when attempting to parse the following date string on, for example, March 31st 2009.
3611 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3612 </code></pre>
3613      * @property defaults
3614      * @type Object
3615      */
3616     defaults: {},
3617
3618     /**
3619      * @property {String[]} dayNames
3620      * An array of textual day names.
3621      * Override these values for international dates.
3622      * Example:
3623      * <pre><code>
3624 Ext.Date.dayNames = [
3625     'SundayInYourLang',
3626     'MondayInYourLang',
3627     ...
3628 ];
3629 </code></pre>
3630      */
3631     dayNames : [
3632         "Sunday",
3633         "Monday",
3634         "Tuesday",
3635         "Wednesday",
3636         "Thursday",
3637         "Friday",
3638         "Saturday"
3639     ],
3640
3641     /**
3642      * @property {String[]} monthNames
3643      * An array of textual month names.
3644      * Override these values for international dates.
3645      * Example:
3646      * <pre><code>
3647 Ext.Date.monthNames = [
3648     'JanInYourLang',
3649     'FebInYourLang',
3650     ...
3651 ];
3652 </code></pre>
3653      */
3654     monthNames : [
3655         "January",
3656         "February",
3657         "March",
3658         "April",
3659         "May",
3660         "June",
3661         "July",
3662         "August",
3663         "September",
3664         "October",
3665         "November",
3666         "December"
3667     ],
3668
3669     /**
3670      * @property {Object} monthNumbers
3671      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3672      * Override these values for international dates.
3673      * Example:
3674      * <pre><code>
3675 Ext.Date.monthNumbers = {
3676     'ShortJanNameInYourLang':0,
3677     'ShortFebNameInYourLang':1,
3678     ...
3679 };
3680 </code></pre>
3681      */
3682     monthNumbers : {
3683         Jan:0,
3684         Feb:1,
3685         Mar:2,
3686         Apr:3,
3687         May:4,
3688         Jun:5,
3689         Jul:6,
3690         Aug:7,
3691         Sep:8,
3692         Oct:9,
3693         Nov:10,
3694         Dec:11
3695     },
3696     /**
3697      * @property {String} defaultFormat
3698      * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
3699      * and {@link Ext.util.Format#date} functions use.  See {@link Ext.Date} for details.</p>
3700      * <p>This may be overridden in a locale file.</p>
3701      */
3702     defaultFormat : "m/d/Y",
3703     /**
3704      * Get the short month name for the given month number.
3705      * Override this function for international dates.
3706      * @param {Number} month A zero-based javascript month number.
3707      * @return {String} The short month name.
3708      */
3709     getShortMonthName : function(month) {
3710         return utilDate.monthNames[month].substring(0, 3);
3711     },
3712
3713     /**
3714      * Get the short day name for the given day number.
3715      * Override this function for international dates.
3716      * @param {Number} day A zero-based javascript day number.
3717      * @return {String} The short day name.
3718      */
3719     getShortDayName : function(day) {
3720         return utilDate.dayNames[day].substring(0, 3);
3721     },
3722
3723     /**
3724      * Get the zero-based javascript month number for the given short/full month name.
3725      * Override this function for international dates.
3726      * @param {String} name The short/full month name.
3727      * @return {Number} The zero-based javascript month number.
3728      */
3729     getMonthNumber : function(name) {
3730         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3731         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3732     },
3733
3734     /**
3735      * Checks if the specified format contains hour information
3736      * @param {String} format The format to check
3737      * @return {Boolean} True if the format contains hour information
3738      * @method
3739      */
3740     formatContainsHourInfo : (function(){
3741         var stripEscapeRe = /(\\.)/g,
3742             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3743         return function(format){
3744             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3745         };
3746     })(),
3747
3748     /**
3749      * Checks if the specified format contains information about
3750      * anything other than the time.
3751      * @param {String} format The format to check
3752      * @return {Boolean} True if the format contains information about
3753      * date/day information.
3754      * @method
3755      */
3756     formatContainsDateInfo : (function(){
3757         var stripEscapeRe = /(\\.)/g,
3758             dateInfoRe = /([djzmnYycU]|MS)/;
3759
3760         return function(format){
3761             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3762         };
3763     })(),
3764
3765     /**
3766      * The base format-code to formatting-function hashmap used by the {@link #format} method.
3767      * Formatting functions are strings (or functions which return strings) which
3768      * will return the appropriate value when evaluated in the context of the Date object
3769      * from which the {@link #format} method is called.
3770      * Add to / override these mappings for custom date formatting.
3771      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3772      * Example:
3773      * <pre><code>
3774 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3775 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3776 </code></pre>
3777      * @type Object
3778      */
3779     formatCodes : {
3780         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3781         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3782         j: "this.getDate()",
3783         l: "Ext.Date.dayNames[this.getDay()]",
3784         N: "(this.getDay() ? this.getDay() : 7)",
3785         S: "Ext.Date.getSuffix(this)",
3786         w: "this.getDay()",
3787         z: "Ext.Date.getDayOfYear(this)",
3788         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3789         F: "Ext.Date.monthNames[this.getMonth()]",
3790         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3791         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3792         n: "(this.getMonth() + 1)",
3793         t: "Ext.Date.getDaysInMonth(this)",
3794         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3795         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3796         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3797         y: "('' + this.getFullYear()).substring(2, 4)",
3798         a: "(this.getHours() < 12 ? 'am' : 'pm')",
3799         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3800         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3801         G: "this.getHours()",
3802         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3803         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3804         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3805         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3806         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3807         O: "Ext.Date.getGMTOffset(this)",
3808         P: "Ext.Date.getGMTOffset(this, true)",
3809         T: "Ext.Date.getTimezone(this)",
3810         Z: "(this.getTimezoneOffset() * -60)",
3811
3812         c: function() { // ISO-8601 -- GMT format
3813             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3814                 var e = c.charAt(i);
3815                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3816             }
3817             return code.join(" + ");
3818         },
3819         /*
3820         c: function() { // ISO-8601 -- UTC format
3821             return [
3822               "this.getUTCFullYear()", "'-'",
3823               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3824               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3825               "'T'",
3826               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3827               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3828               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3829               "'Z'"
3830             ].join(" + ");
3831         },
3832         */
3833
3834         U: "Math.round(this.getTime() / 1000)"
3835     },
3836
3837     /**
3838      * Checks if the passed Date parameters will cause a javascript Date "rollover".
3839      * @param {Number} year 4-digit year
3840      * @param {Number} month 1-based month-of-year
3841      * @param {Number} day Day of month
3842      * @param {Number} hour (optional) Hour
3843      * @param {Number} minute (optional) Minute
3844      * @param {Number} second (optional) Second
3845      * @param {Number} millisecond (optional) Millisecond
3846      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3847      */
3848     isValid : function(y, m, d, h, i, s, ms) {
3849         // setup defaults
3850         h = h || 0;
3851         i = i || 0;
3852         s = s || 0;
3853         ms = ms || 0;
3854
3855         // Special handling for year < 100
3856         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3857
3858         return y == dt.getFullYear() &&
3859             m == dt.getMonth() + 1 &&
3860             d == dt.getDate() &&
3861             h == dt.getHours() &&
3862             i == dt.getMinutes() &&
3863             s == dt.getSeconds() &&
3864             ms == dt.getMilliseconds();
3865     },
3866
3867     /**
3868      * Parses the passed string using the specified date format.
3869      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3870      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3871      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3872      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3873      * Keep in mind that the input date string must precisely match the specified format string
3874      * in order for the parse operation to be successful (failed parse operations return a null value).
3875      * <p>Example:</p><pre><code>
3876 //dt = Fri May 25 2007 (current date)
3877 var dt = new Date();
3878
3879 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
3880 dt = Ext.Date.parse("2006", "Y");
3881
3882 //dt = Sun Jan 15 2006 (all date parts specified)
3883 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3884
3885 //dt = Sun Jan 15 2006 15:20:01
3886 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3887
3888 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3889 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3890 </code></pre>
3891      * @param {String} input The raw date string.
3892      * @param {String} format The expected date string format.
3893      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3894                         (defaults to false). Invalid date strings will return null when parsed.
3895      * @return {Date} The parsed Date.
3896      */
3897     parse : function(input, format, strict) {
3898         var p = utilDate.parseFunctions;
3899         if (p[format] == null) {
3900             utilDate.createParser(format);
3901         }
3902         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3903     },
3904
3905     // Backwards compat
3906     parseDate: function(input, format, strict){
3907         return utilDate.parse(input, format, strict);
3908     },
3909
3910
3911     // private
3912     getFormatCode : function(character) {
3913         var f = utilDate.formatCodes[character];
3914
3915         if (f) {
3916           f = typeof f == 'function'? f() : f;
3917           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3918         }
3919
3920         // note: unknown characters are treated as literals
3921         return f || ("'" + Ext.String.escape(character) + "'");
3922     },
3923
3924     // private
3925     createFormat : function(format) {
3926         var code = [],
3927             special = false,
3928             ch = '';
3929
3930         for (var i = 0; i < format.length; ++i) {
3931             ch = format.charAt(i);
3932             if (!special && ch == "\\") {
3933                 special = true;
3934             } else if (special) {
3935                 special = false;
3936                 code.push("'" + Ext.String.escape(ch) + "'");
3937             } else {
3938                 code.push(utilDate.getFormatCode(ch));
3939             }
3940         }
3941         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3942     },
3943
3944     // private
3945     createParser : (function() {
3946         var code = [
3947             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3948                 "def = Ext.Date.defaults,",
3949                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3950
3951             "if(results){",
3952                 "{1}",
3953
3954                 "if(u != null){", // i.e. unix time is defined
3955                     "v = new Date(u * 1000);", // give top priority to UNIX time
3956                 "}else{",
3957                     // create Date object representing midnight of the current day;
3958                     // this will provide us with our date defaults
3959                     // (note: clearTime() handles Daylight Saving Time automatically)
3960                     "dt = Ext.Date.clearTime(new Date);",
3961
3962                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
3963                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3964                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3965                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3966
3967                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
3968                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3969                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3970                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3971                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3972
3973                     "if(z >= 0 && y >= 0){",
3974                         // both the year and zero-based day of year are defined and >= 0.
3975                         // these 2 values alone provide sufficient info to create a full date object
3976
3977                         // create Date object representing January 1st for the given year
3978                         // handle years < 100 appropriately
3979                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3980
3981                         // then add day of year, checking for Date "rollover" if necessary
3982                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3983                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3984                         "v = null;", // invalid date, so return null
3985                     "}else{",
3986                         // plain old Date object
3987                         // handle years < 100 properly
3988                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3989                     "}",
3990                 "}",
3991             "}",
3992
3993             "if(v){",
3994                 // favour UTC offset over GMT offset
3995                 "if(zz != null){",
3996                     // reset to UTC, then add offset
3997                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3998                 "}else if(o){",
3999                     // reset to GMT, then add offset
4000                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
4001                 "}",
4002             "}",
4003
4004             "return v;"
4005         ].join('\n');
4006
4007         return function(format) {
4008             var regexNum = utilDate.parseRegexes.length,
4009                 currentGroup = 1,
4010                 calc = [],
4011                 regex = [],
4012                 special = false,
4013                 ch = "";
4014
4015             for (var i = 0; i < format.length; ++i) {
4016                 ch = format.charAt(i);
4017                 if (!special && ch == "\\") {
4018                     special = true;
4019                 } else if (special) {
4020                     special = false;
4021                     regex.push(Ext.String.escape(ch));
4022                 } else {
4023                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
4024                     currentGroup += obj.g;
4025                     regex.push(obj.s);
4026                     if (obj.g && obj.c) {
4027                         calc.push(obj.c);
4028                     }
4029                 }
4030             }
4031
4032             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
4033             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
4034         };
4035     })(),
4036
4037     // private
4038     parseCodes : {
4039         /*
4040          * Notes:
4041          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
4042          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
4043          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
4044          */
4045         d: {
4046             g:1,
4047             c:"d = parseInt(results[{0}], 10);\n",
4048             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
4049         },
4050         j: {
4051             g:1,
4052             c:"d = parseInt(results[{0}], 10);\n",
4053             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
4054         },
4055         D: function() {
4056             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
4057             return {
4058                 g:0,
4059                 c:null,
4060                 s:"(?:" + a.join("|") +")"
4061             };
4062         },
4063         l: function() {
4064             return {
4065                 g:0,
4066                 c:null,
4067                 s:"(?:" + utilDate.dayNames.join("|") + ")"
4068             };
4069         },
4070         N: {
4071             g:0,
4072             c:null,
4073             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
4074         },
4075         S: {
4076             g:0,
4077             c:null,
4078             s:"(?:st|nd|rd|th)"
4079         },
4080         w: {
4081             g:0,
4082             c:null,
4083             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
4084         },
4085         z: {
4086             g:1,
4087             c:"z = parseInt(results[{0}], 10);\n",
4088             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
4089         },
4090         W: {
4091             g:0,
4092             c:null,
4093             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
4094         },
4095         F: function() {
4096             return {
4097                 g:1,
4098                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
4099                 s:"(" + utilDate.monthNames.join("|") + ")"
4100             };
4101         },
4102         M: function() {
4103             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
4104             return Ext.applyIf({
4105                 s:"(" + a.join("|") + ")"
4106             }, utilDate.formatCodeToRegex("F"));
4107         },
4108         m: {
4109             g:1,
4110             c:"m = parseInt(results[{0}], 10) - 1;\n",
4111             s:"(\\d{2})" // month number with leading zeros (01 - 12)
4112         },
4113         n: {
4114             g:1,
4115             c:"m = parseInt(results[{0}], 10) - 1;\n",
4116             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
4117         },
4118         t: {
4119             g:0,
4120             c:null,
4121             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
4122         },
4123         L: {
4124             g:0,
4125             c:null,
4126             s:"(?:1|0)"
4127         },
4128         o: function() {
4129             return utilDate.formatCodeToRegex("Y");
4130         },
4131         Y: {
4132             g:1,
4133             c:"y = parseInt(results[{0}], 10);\n",
4134             s:"(\\d{4})" // 4-digit year
4135         },
4136         y: {
4137             g:1,
4138             c:"var ty = parseInt(results[{0}], 10);\n"
4139                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
4140             s:"(\\d{1,2})"
4141         },
4142         /*
4143          * In the am/pm parsing routines, we allow both upper and lower case
4144          * even though it doesn't exactly match the spec. It gives much more flexibility
4145          * in being able to specify case insensitive regexes.
4146          */
4147         a: {
4148             g:1,
4149             c:"if (/(am)/i.test(results[{0}])) {\n"
4150                 + "if (!h || h == 12) { h = 0; }\n"
4151                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4152             s:"(am|pm|AM|PM)"
4153         },
4154         A: {
4155             g:1,
4156             c:"if (/(am)/i.test(results[{0}])) {\n"
4157                 + "if (!h || h == 12) { h = 0; }\n"
4158                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4159             s:"(AM|PM|am|pm)"
4160         },
4161         g: function() {
4162             return utilDate.formatCodeToRegex("G");
4163         },
4164         G: {
4165             g:1,
4166             c:"h = parseInt(results[{0}], 10);\n",
4167             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
4168         },
4169         h: function() {
4170             return utilDate.formatCodeToRegex("H");
4171         },
4172         H: {
4173             g:1,
4174             c:"h = parseInt(results[{0}], 10);\n",
4175             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
4176         },
4177         i: {
4178             g:1,
4179             c:"i = parseInt(results[{0}], 10);\n",
4180             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
4181         },
4182         s: {
4183             g:1,
4184             c:"s = parseInt(results[{0}], 10);\n",
4185             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
4186         },
4187         u: {
4188             g:1,
4189             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
4190             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4191         },
4192         O: {
4193             g:1,
4194             c:[
4195                 "o = results[{0}];",
4196                 "var sn = o.substring(0,1),", // get + / - sign
4197                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4198                     "mn = o.substring(3,5) % 60;", // get minutes
4199                 "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
4200             ].join("\n"),
4201             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
4202         },
4203         P: {
4204             g:1,
4205             c:[
4206                 "o = results[{0}];",
4207                 "var sn = o.substring(0,1),", // get + / - sign
4208                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4209                     "mn = o.substring(4,6) % 60;", // get minutes
4210                 "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
4211             ].join("\n"),
4212             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
4213         },
4214         T: {
4215             g:0,
4216             c:null,
4217             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
4218         },
4219         Z: {
4220             g:1,
4221             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
4222                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
4223             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
4224         },
4225         c: function() {
4226             var calc = [],
4227                 arr = [
4228                     utilDate.formatCodeToRegex("Y", 1), // year
4229                     utilDate.formatCodeToRegex("m", 2), // month
4230                     utilDate.formatCodeToRegex("d", 3), // day
4231                     utilDate.formatCodeToRegex("h", 4), // hour
4232                     utilDate.formatCodeToRegex("i", 5), // minute
4233                     utilDate.formatCodeToRegex("s", 6), // second
4234                     {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)
4235                     {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
4236                         "if(results[8]) {", // timezone specified
4237                             "if(results[8] == 'Z'){",
4238                                 "zz = 0;", // UTC
4239                             "}else if (results[8].indexOf(':') > -1){",
4240                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
4241                             "}else{",
4242                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
4243                             "}",
4244                         "}"
4245                     ].join('\n')}
4246                 ];
4247
4248             for (var i = 0, l = arr.length; i < l; ++i) {
4249                 calc.push(arr[i].c);
4250             }
4251
4252             return {
4253                 g:1,
4254                 c:calc.join(""),
4255                 s:[
4256                     arr[0].s, // year (required)
4257                     "(?:", "-", arr[1].s, // month (optional)
4258                         "(?:", "-", arr[2].s, // day (optional)
4259                             "(?:",
4260                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
4261                                 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
4262                                 "(?::", arr[5].s, ")?", // seconds (optional)
4263                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
4264                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
4265                             ")?",
4266                         ")?",
4267                     ")?"
4268                 ].join("")
4269             };
4270         },
4271         U: {
4272             g:1,
4273             c:"u = parseInt(results[{0}], 10);\n",
4274             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
4275         }
4276     },
4277
4278     //Old Ext.Date prototype methods.
4279     // private
4280     dateFormat: function(date, format) {
4281         return utilDate.format(date, format);
4282     },
4283
4284     /**
4285      * Formats a date given the supplied format string.
4286      * @param {Date} date The date to format
4287      * @param {String} format The format string
4288      * @return {String} The formatted date
4289      */
4290     format: function(date, format) {
4291         if (utilDate.formatFunctions[format] == null) {
4292             utilDate.createFormat(format);
4293         }
4294         var result = utilDate.formatFunctions[format].call(date);
4295         return result + '';
4296     },
4297
4298     /**
4299      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
4300      *
4301      * Note: The date string returned by the javascript Date object's toString() method varies
4302      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
4303      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
4304      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
4305      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
4306      * from the GMT offset portion of the date string.
4307      * @param {Date} date The date
4308      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
4309      */
4310     getTimezone : function(date) {
4311         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
4312         //
4313         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
4314         // 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)
4315         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
4316         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
4317         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
4318         //
4319         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
4320         // step 1: (?:\((.*)\) -- find timezone in parentheses
4321         // 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
4322         // step 3: remove all non uppercase characters found in step 1 and 2
4323         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
4324     },
4325
4326     /**
4327      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
4328      * @param {Date} date The date
4329      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
4330      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
4331      */
4332     getGMTOffset : function(date, colon) {
4333         var offset = date.getTimezoneOffset();
4334         return (offset > 0 ? "-" : "+")
4335             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
4336             + (colon ? ":" : "")
4337             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
4338     },
4339
4340     /**
4341      * Get the numeric day number of the year, adjusted for leap year.
4342      * @param {Date} date The date
4343      * @return {Number} 0 to 364 (365 in leap years).
4344      */
4345     getDayOfYear: function(date) {
4346         var num = 0,
4347             d = Ext.Date.clone(date),
4348             m = date.getMonth(),
4349             i;
4350
4351         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4352             num += utilDate.getDaysInMonth(d);
4353         }
4354         return num + date.getDate() - 1;
4355     },
4356
4357     /**
4358      * Get the numeric ISO-8601 week number of the year.
4359      * (equivalent to the format specifier 'W', but without a leading zero).
4360      * @param {Date} date The date
4361      * @return {Number} 1 to 53
4362      * @method
4363      */
4364     getWeekOfYear : (function() {
4365         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4366         var ms1d = 864e5, // milliseconds in a day
4367             ms7d = 7 * ms1d; // milliseconds in a week
4368
4369         return function(date) { // return a closure so constants get calculated only once
4370             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4371                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4372                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4373
4374             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4375         };
4376     })(),
4377
4378     /**
4379      * Checks if the current date falls within a leap year.
4380      * @param {Date} date The date
4381      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4382      */
4383     isLeapYear : function(date) {
4384         var year = date.getFullYear();
4385         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4386     },
4387
4388     /**
4389      * Get the first day of the current month, adjusted for leap year.  The returned value
4390      * is the numeric day index within the week (0-6) which can be used in conjunction with
4391      * the {@link #monthNames} array to retrieve the textual day name.
4392      * Example:
4393      * <pre><code>
4394 var dt = new Date('1/10/2007'),
4395     firstDay = Ext.Date.getFirstDayOfMonth(dt);
4396 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4397      * </code></pre>
4398      * @param {Date} date The date
4399      * @return {Number} The day number (0-6).
4400      */
4401     getFirstDayOfMonth : function(date) {
4402         var day = (date.getDay() - (date.getDate() - 1)) % 7;
4403         return (day < 0) ? (day + 7) : day;
4404     },
4405
4406     /**
4407      * Get the last day of the current month, adjusted for leap year.  The returned value
4408      * is the numeric day index within the week (0-6) which can be used in conjunction with
4409      * the {@link #monthNames} array to retrieve the textual day name.
4410      * Example:
4411      * <pre><code>
4412 var dt = new Date('1/10/2007'),
4413     lastDay = Ext.Date.getLastDayOfMonth(dt);
4414 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4415      * </code></pre>
4416      * @param {Date} date The date
4417      * @return {Number} The day number (0-6).
4418      */
4419     getLastDayOfMonth : function(date) {
4420         return utilDate.getLastDateOfMonth(date).getDay();
4421     },
4422
4423
4424     /**
4425      * Get the date of the first day of the month in which this date resides.
4426      * @param {Date} date The date
4427      * @return {Date}
4428      */
4429     getFirstDateOfMonth : function(date) {
4430         return new Date(date.getFullYear(), date.getMonth(), 1);
4431     },
4432
4433     /**
4434      * Get the date of the last day of the month in which this date resides.
4435      * @param {Date} date The date
4436      * @return {Date}
4437      */
4438     getLastDateOfMonth : function(date) {
4439         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4440     },
4441
4442     /**
4443      * Get the number of days in the current month, adjusted for leap year.
4444      * @param {Date} date The date
4445      * @return {Number} The number of days in the month.
4446      * @method
4447      */
4448     getDaysInMonth: (function() {
4449         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4450
4451         return function(date) { // return a closure for efficiency
4452             var m = date.getMonth();
4453
4454             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4455         };
4456     })(),
4457
4458     /**
4459      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4460      * @param {Date} date The date
4461      * @return {String} 'st, 'nd', 'rd' or 'th'.
4462      */
4463     getSuffix : function(date) {
4464         switch (date.getDate()) {
4465             case 1:
4466             case 21:
4467             case 31:
4468                 return "st";
4469             case 2:
4470             case 22:
4471                 return "nd";
4472             case 3:
4473             case 23:
4474                 return "rd";
4475             default:
4476                 return "th";
4477         }
4478     },
4479
4480     /**
4481      * Creates and returns a new Date instance with the exact same date value as the called instance.
4482      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4483      * variable will also be changed.  When the intention is to create a new variable that will not
4484      * modify the original instance, you should create a clone.
4485      *
4486      * Example of correctly cloning a date:
4487      * <pre><code>
4488 //wrong way:
4489 var orig = new Date('10/1/2006');
4490 var copy = orig;
4491 copy.setDate(5);
4492 console.log(orig);  //returns 'Thu Oct 05 2006'!
4493
4494 //correct way:
4495 var orig = new Date('10/1/2006'),
4496     copy = Ext.Date.clone(orig);
4497 copy.setDate(5);
4498 console.log(orig);  //returns 'Thu Oct 01 2006'
4499      * </code></pre>
4500      * @param {Date} date The date
4501      * @return {Date} The new Date instance.
4502      */
4503     clone : function(date) {
4504         return new Date(date.getTime());
4505     },
4506
4507     /**
4508      * Checks if the current date is affected by Daylight Saving Time (DST).
4509      * @param {Date} date The date
4510      * @return {Boolean} True if the current date is affected by DST.
4511      */
4512     isDST : function(date) {
4513         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4514         // courtesy of @geoffrey.mcgill
4515         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4516     },
4517
4518     /**
4519      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4520      * automatically adjusting for Daylight Saving Time (DST) where applicable.
4521      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4522      * @param {Date} date The date
4523      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4524      * @return {Date} this or the clone.
4525      */
4526     clearTime : function(date, clone) {
4527         if (clone) {
4528             return Ext.Date.clearTime(Ext.Date.clone(date));
4529         }
4530
4531         // get current date before clearing time
4532         var d = date.getDate();
4533
4534         // clear time
4535         date.setHours(0);
4536         date.setMinutes(0);
4537         date.setSeconds(0);
4538         date.setMilliseconds(0);
4539
4540         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4541             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4542             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4543
4544             // increment hour until cloned date == current date
4545             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4546
4547             date.setDate(d);
4548             date.setHours(c.getHours());
4549         }
4550
4551         return date;
4552     },
4553
4554     /**
4555      * Provides a convenient method for performing basic date arithmetic. This method
4556      * does not modify the Date instance being called - it creates and returns
4557      * a new Date instance containing the resulting date value.
4558      *
4559      * Examples:
4560      * <pre><code>
4561 // Basic usage:
4562 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4563 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4564
4565 // Negative values will be subtracted:
4566 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4567 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4568
4569      * </code></pre>
4570      *
4571      * @param {Date} date The date to modify
4572      * @param {String} interval A valid date interval enum value.
4573      * @param {Number} value The amount to add to the current date.
4574      * @return {Date} The new Date instance.
4575      */
4576     add : function(date, interval, value) {
4577         var d = Ext.Date.clone(date),
4578             Date = Ext.Date;
4579         if (!interval || value === 0) return d;
4580
4581         switch(interval.toLowerCase()) {
4582             case Ext.Date.MILLI:
4583                 d.setMilliseconds(d.getMilliseconds() + value);
4584                 break;
4585             case Ext.Date.SECOND:
4586                 d.setSeconds(d.getSeconds() + value);
4587                 break;
4588             case Ext.Date.MINUTE:
4589                 d.setMinutes(d.getMinutes() + value);
4590                 break;
4591             case Ext.Date.HOUR:
4592                 d.setHours(d.getHours() + value);
4593                 break;
4594             case Ext.Date.DAY:
4595                 d.setDate(d.getDate() + value);
4596                 break;
4597             case Ext.Date.MONTH:
4598                 var day = date.getDate();
4599                 if (day > 28) {
4600                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4601                 }
4602                 d.setDate(day);
4603                 d.setMonth(date.getMonth() + value);
4604                 break;
4605             case Ext.Date.YEAR:
4606                 d.setFullYear(date.getFullYear() + value);
4607                 break;
4608         }
4609         return d;
4610     },
4611
4612     /**
4613      * Checks if a date falls on or between the given start and end dates.
4614      * @param {Date} date The date to check
4615      * @param {Date} start Start date
4616      * @param {Date} end End date
4617      * @return {Boolean} true if this date falls on or between the given start and end dates.
4618      */
4619     between : function(date, start, end) {
4620         var t = date.getTime();
4621         return start.getTime() <= t && t <= end.getTime();
4622     },
4623
4624     //Maintains compatibility with old static and prototype window.Date methods.
4625     compat: function() {
4626         var nativeDate = window.Date,
4627             p, u,
4628             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'],
4629             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4630
4631         //Append statics
4632         Ext.Array.forEach(statics, function(s) {
4633             nativeDate[s] = utilDate[s];
4634         });
4635
4636         //Append to prototype
4637         Ext.Array.forEach(proto, function(s) {
4638             nativeDate.prototype[s] = function() {
4639                 var args = Array.prototype.slice.call(arguments);
4640                 args.unshift(this);
4641                 return utilDate[s].apply(utilDate, args);
4642             };
4643         });
4644     }
4645 };
4646
4647 var utilDate = Ext.Date;
4648
4649 })();
4650
4651 /**
4652  * @author Jacky Nguyen <jacky@sencha.com>
4653  * @docauthor Jacky Nguyen <jacky@sencha.com>
4654  * @class Ext.Base
4655  *
4656  * The root of all classes created with {@link Ext#define}.
4657  *
4658  * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
4659  * All prototype and static members of this class are inherited by all other classes.
4660  */
4661 (function(flexSetter) {
4662
4663 var Base = Ext.Base = function() {};
4664     Base.prototype = {
4665         $className: 'Ext.Base',
4666
4667         $class: Base,
4668
4669         /**
4670          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4671          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4672          * for a detailed comparison
4673          *
4674          *     Ext.define('My.Cat', {
4675          *         statics: {
4676          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4677          *         },
4678          *
4679          *         constructor: function() {
4680          *             alert(this.self.speciesName); / dependent on 'this'
4681          *
4682          *             return this;
4683          *         },
4684          *
4685          *         clone: function() {
4686          *             return new this.self();
4687          *         }
4688          *     });
4689          *
4690          *
4691          *     Ext.define('My.SnowLeopard', {
4692          *         extend: 'My.Cat',
4693          *         statics: {
4694          *             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
4695          *         }
4696          *     });
4697          *
4698          *     var cat = new My.Cat();                     // alerts 'Cat'
4699          *     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
4700          *
4701          *     var clone = snowLeopard.clone();
4702          *     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
4703          *
4704          * @type Ext.Class
4705          * @protected
4706          */
4707         self: Base,
4708
4709         // Default constructor, simply returns `this`
4710         constructor: function() {
4711             return this;
4712         },
4713
4714         //<feature classSystem.config>
4715         /**
4716          * Initialize configuration for this class. a typical example:
4717          *
4718          *     Ext.define('My.awesome.Class', {
4719          *         // The default config
4720          *         config: {
4721          *             name: 'Awesome',
4722          *             isAwesome: true
4723          *         },
4724          *
4725          *         constructor: function(config) {
4726          *             this.initConfig(config);
4727          *
4728          *             return this;
4729          *         }
4730          *     });
4731          *
4732          *     var awesome = new My.awesome.Class({
4733          *         name: 'Super Awesome'
4734          *     });
4735          *
4736          *     alert(awesome.getName()); // 'Super Awesome'
4737          *
4738          * @protected
4739          * @param {Object} config
4740          * @return {Object} mixins The mixin prototypes as key - value pairs
4741          */
4742         initConfig: function(config) {
4743             if (!this.$configInited) {
4744                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4745
4746                 this.applyConfig(this.config);
4747
4748                 this.$configInited = true;
4749             }
4750
4751             return this;
4752         },
4753
4754         /**
4755          * @private
4756          */
4757         setConfig: function(config) {
4758             this.applyConfig(config || {});
4759
4760             return this;
4761         },
4762
4763         /**
4764          * @private
4765          */
4766         applyConfig: flexSetter(function(name, value) {
4767             var setter = 'set' + Ext.String.capitalize(name);
4768
4769             if (typeof this[setter] === 'function') {
4770                 this[setter].call(this, value);
4771             }
4772
4773             return this;
4774         }),
4775         //</feature>
4776
4777         /**
4778          * Call the parent's overridden method. For example:
4779          *
4780          *     Ext.define('My.own.A', {
4781          *         constructor: function(test) {
4782          *             alert(test);
4783          *         }
4784          *     });
4785          *
4786          *     Ext.define('My.own.B', {
4787          *         extend: 'My.own.A',
4788          *
4789          *         constructor: function(test) {
4790          *             alert(test);
4791          *
4792          *             this.callParent([test + 1]);
4793          *         }
4794          *     });
4795          *
4796          *     Ext.define('My.own.C', {
4797          *         extend: 'My.own.B',
4798          *
4799          *         constructor: function() {
4800          *             alert("Going to call parent's overriden constructor...");
4801          *
4802          *             this.callParent(arguments);
4803          *         }
4804          *     });
4805          *
4806          *     var a = new My.own.A(1); // alerts '1'
4807          *     var b = new My.own.B(1); // alerts '1', then alerts '2'
4808          *     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4809          *                              // alerts '2', then alerts '3'
4810          *
4811          * @protected
4812          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4813          * from the current method, for example: `this.callParent(arguments)`
4814          * @return {Object} Returns the result from the superclass' method
4815          */
4816         callParent: function(args) {
4817             var method = this.callParent.caller,
4818                 parentClass, methodName;
4819
4820             if (!method.$owner) {
4821                 if (!method.caller) {
4822                     Ext.Error.raise({
4823                         sourceClass: Ext.getClassName(this),
4824                         sourceMethod: "callParent",
4825                         msg: "Attempting to call a protected method from the public scope, which is not allowed"
4826                     });
4827                 }
4828
4829                 method = method.caller;
4830             }
4831
4832             parentClass = method.$owner.superclass;
4833             methodName = method.$name;
4834
4835             if (!(methodName in parentClass)) {
4836                 Ext.Error.raise({
4837                     sourceClass: Ext.getClassName(this),
4838                     sourceMethod: methodName,
4839                     msg: "this.callParent() was called but there's no such method (" + methodName +
4840                          ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4841                  });
4842             }
4843
4844             return parentClass[methodName].apply(this, args || []);
4845         },
4846
4847
4848         /**
4849          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4850          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4851          * `this` points to during run-time
4852          *
4853          *     Ext.define('My.Cat', {
4854          *         statics: {
4855          *             totalCreated: 0,
4856          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4857          *         },
4858          *
4859          *         constructor: function() {
4860          *             var statics = this.statics();
4861          *
4862          *             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
4863          *                                             // equivalent to: My.Cat.speciesName
4864          *
4865          *             alert(this.self.speciesName);   // dependent on 'this'
4866          *
4867          *             statics.totalCreated++;
4868          *
4869          *             return this;
4870          *         },
4871          *
4872          *         clone: function() {
4873          *             var cloned = new this.self;                      // dependent on 'this'
4874          *
4875          *             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
4876          *
4877          *             return cloned;
4878          *         }
4879          *     });
4880          *
4881          *
4882          *     Ext.define('My.SnowLeopard', {
4883          *         extend: 'My.Cat',
4884          *
4885          *         statics: {
4886          *             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
4887          *         },
4888          *
4889          *         constructor: function() {
4890          *             this.callParent();
4891          *         }
4892          *     });
4893          *
4894          *     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
4895          *
4896          *     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4897          *
4898          *     var clone = snowLeopard.clone();
4899          *     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
4900          *     alert(clone.groupName);                 // alerts 'Cat'
4901          *
4902          *     alert(My.Cat.totalCreated);             // alerts 3
4903          *
4904          * @protected
4905          * @return {Ext.Class}
4906          */
4907         statics: function() {
4908             var method = this.statics.caller,
4909                 self = this.self;
4910
4911             if (!method) {
4912                 return self;
4913             }
4914
4915             return method.$owner;
4916         },
4917
4918         /**
4919          * Call the original method that was previously overridden with {@link Ext.Base#override}
4920          *
4921          *     Ext.define('My.Cat', {
4922          *         constructor: function() {
4923          *             alert("I'm a cat!");
4924          *
4925          *             return this;
4926          *         }
4927          *     });
4928          *
4929          *     My.Cat.override({
4930          *         constructor: function() {
4931          *             alert("I'm going to be a cat!");
4932          *
4933          *             var instance = this.callOverridden();
4934          *
4935          *             alert("Meeeeoooowwww");
4936          *
4937          *             return instance;
4938          *         }
4939          *     });
4940          *
4941          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4942          *                               // alerts "I'm a cat!"
4943          *                               // alerts "Meeeeoooowwww"
4944          *
4945          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4946          * @return {Object} Returns the result after calling the overridden method
4947          * @protected
4948          */
4949         callOverridden: function(args) {
4950             var method = this.callOverridden.caller;
4951
4952             if (!method.$owner) {
4953                 Ext.Error.raise({
4954                     sourceClass: Ext.getClassName(this),
4955                     sourceMethod: "callOverridden",
4956                     msg: "Attempting to call a protected method from the public scope, which is not allowed"
4957                 });
4958             }
4959
4960             if (!method.$previous) {
4961                 Ext.Error.raise({
4962                     sourceClass: Ext.getClassName(this),
4963                     sourceMethod: "callOverridden",
4964                     msg: "this.callOverridden was called in '" + method.$name +
4965                          "' but this method has never been overridden"
4966                  });
4967             }
4968
4969             return method.$previous.apply(this, args || []);
4970         },
4971
4972         destroy: function() {}
4973     };
4974
4975     // These static properties will be copied to every newly created class with {@link Ext#define}
4976     Ext.apply(Ext.Base, {
4977         /**
4978          * Create a new instance of this Class.
4979          *
4980          *     Ext.define('My.cool.Class', {
4981          *         ...
4982          *     });
4983          *
4984          *     My.cool.Class.create({
4985          *         someConfig: true
4986          *     });
4987          *
4988          * All parameters are passed to the constructor of the class.
4989          *
4990          * @return {Object} the created instance.
4991          * @static
4992          * @inheritable
4993          */
4994         create: function() {
4995             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4996         },
4997
4998         /**
4999          * @private
5000          * @inheritable
5001          */
5002         own: function(name, value) {
5003             if (typeof value == 'function') {
5004                 this.ownMethod(name, value);
5005             }
5006             else {
5007                 this.prototype[name] = value;
5008             }
5009         },
5010
5011         /**
5012          * @private
5013          * @inheritable
5014          */
5015         ownMethod: function(name, fn) {
5016             var originalFn;
5017
5018             if (typeof fn.$owner !== 'undefined' && fn !== Ext.emptyFn) {
5019                 originalFn = fn;
5020
5021                 fn = function() {
5022                     return originalFn.apply(this, arguments);
5023                 };
5024             }
5025
5026             var className;
5027             className = Ext.getClassName(this);
5028             if (className) {
5029                 fn.displayName = className + '#' + name;
5030             }
5031             fn.$owner = this;
5032             fn.$name = name;
5033
5034             this.prototype[name] = fn;
5035         },
5036
5037         /**
5038          * Add / override static properties of this class.
5039          *
5040          *     Ext.define('My.cool.Class', {
5041          *         ...
5042          *     });
5043          *
5044          *     My.cool.Class.addStatics({
5045          *         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
5046          *         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
5047          *         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
5048          *     });
5049          *
5050          * @param {Object} members
5051          * @return {Ext.Base} this
5052          * @static
5053          * @inheritable
5054          */
5055         addStatics: function(members) {
5056             for (var name in members) {
5057                 if (members.hasOwnProperty(name)) {
5058                     this[name] = members[name];
5059                 }
5060             }
5061
5062             return this;
5063         },
5064
5065         /**
5066          * @private
5067          * @param {Object} members
5068          */
5069         addInheritableStatics: function(members) {
5070             var inheritableStatics,
5071                 hasInheritableStatics,
5072                 prototype = this.prototype,
5073                 name, member;
5074
5075             inheritableStatics = prototype.$inheritableStatics;
5076             hasInheritableStatics = prototype.$hasInheritableStatics;
5077
5078             if (!inheritableStatics) {
5079                 inheritableStatics = prototype.$inheritableStatics = [];
5080                 hasInheritableStatics = prototype.$hasInheritableStatics = {};
5081             }
5082
5083             var className = Ext.getClassName(this);
5084
5085             for (name in members) {
5086                 if (members.hasOwnProperty(name)) {
5087                     member = members[name];
5088                     if (typeof member == 'function') {
5089                         member.displayName = className + '.' + name;
5090                     }
5091                     this[name] = member;
5092
5093                     if (!hasInheritableStatics[name]) {
5094                         hasInheritableStatics[name] = true;
5095                         inheritableStatics.push(name);
5096                     }
5097                 }
5098             }
5099
5100             return this;
5101         },
5102
5103         /**
5104          * Add methods / properties to the prototype of this class.
5105          *
5106          *     Ext.define('My.awesome.Cat', {
5107          *         constructor: function() {
5108          *             ...
5109          *         }
5110          *     });
5111          *
5112          *      My.awesome.Cat.implement({
5113          *          meow: function() {
5114          *             alert('Meowww...');
5115          *          }
5116          *      });
5117          *
5118          *      var kitty = new My.awesome.Cat;
5119          *      kitty.meow();
5120          *
5121          * @param {Object} members
5122          * @static
5123          * @inheritable
5124          */
5125         implement: function(members) {
5126             var prototype = this.prototype,
5127                 enumerables = Ext.enumerables,
5128                 name, i, member;
5129             var className = Ext.getClassName(this);
5130             for (name in members) {
5131                 if (members.hasOwnProperty(name)) {
5132                     member = members[name];
5133
5134                     if (typeof member === 'function') {
5135                         member.$owner = this;
5136                         member.$name = name;
5137                         if (className) {
5138                             member.displayName = className + '#' + name;
5139                         }
5140                     }
5141
5142                     prototype[name] = member;
5143                 }
5144             }
5145
5146             if (enumerables) {
5147                 for (i = enumerables.length; i--;) {
5148                     name = enumerables[i];
5149
5150                     if (members.hasOwnProperty(name)) {
5151                         member = members[name];
5152                         member.$owner = this;
5153                         member.$name = name;
5154                         prototype[name] = member;
5155                     }
5156                 }
5157             }
5158         },
5159
5160         /**
5161          * Borrow another class' members to the prototype of this class.
5162          *
5163          *     Ext.define('Bank', {
5164          *         money: '$$$',
5165          *         printMoney: function() {
5166          *             alert('$$$$$$$');
5167          *         }
5168          *     });
5169          *
5170          *     Ext.define('Thief', {
5171          *         ...
5172          *     });
5173          *
5174          *     Thief.borrow(Bank, ['money', 'printMoney']);
5175          *
5176          *     var steve = new Thief();
5177          *
5178          *     alert(steve.money); // alerts '$$$'
5179          *     steve.printMoney(); // alerts '$$$$$$$'
5180          *
5181          * @param {Ext.Base} fromClass The class to borrow members from
5182          * @param {String/String[]} members The names of the members to borrow
5183          * @return {Ext.Base} this
5184          * @static
5185          * @inheritable
5186          */
5187         borrow: function(fromClass, members) {
5188             var fromPrototype = fromClass.prototype,
5189                 i, ln, member;
5190
5191             members = Ext.Array.from(members);
5192
5193             for (i = 0, ln = members.length; i < ln; i++) {
5194                 member = members[i];
5195
5196                 this.own(member, fromPrototype[member]);
5197             }
5198
5199             return this;
5200         },
5201
5202         /**
5203          * Override prototype members of this class. Overridden methods can be invoked via
5204          * {@link Ext.Base#callOverridden}
5205          *
5206          *     Ext.define('My.Cat', {
5207          *         constructor: function() {
5208          *             alert("I'm a cat!");
5209          *
5210          *             return this;
5211          *         }
5212          *     });
5213          *
5214          *     My.Cat.override({
5215          *         constructor: function() {
5216          *             alert("I'm going to be a cat!");
5217          *
5218          *             var instance = this.callOverridden();
5219          *
5220          *             alert("Meeeeoooowwww");
5221          *
5222          *             return instance;
5223          *         }
5224          *     });
5225          *
5226          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
5227          *                               // alerts "I'm a cat!"
5228          *                               // alerts "Meeeeoooowwww"
5229          *
5230          * @param {Object} members
5231          * @return {Ext.Base} this
5232          * @static
5233          * @inheritable
5234          */
5235         override: function(members) {
5236             var prototype = this.prototype,
5237                 enumerables = Ext.enumerables,
5238                 name, i, member, previous;
5239
5240             if (arguments.length === 2) {
5241                 name = members;
5242                 member = arguments[1];
5243
5244                 if (typeof member == 'function') {
5245                     if (typeof prototype[name] == 'function') {
5246                         previous = prototype[name];
5247                         member.$previous = previous;
5248                     }
5249
5250                     this.ownMethod(name, member);
5251                 }
5252                 else {
5253                     prototype[name] = member;
5254                 }
5255
5256                 return this;
5257             }
5258
5259             for (name in members) {
5260                 if (members.hasOwnProperty(name)) {
5261                     member = members[name];
5262
5263                     if (typeof member === 'function') {
5264                         if (typeof prototype[name] === 'function') {
5265                             previous = prototype[name];
5266                             member.$previous = previous;
5267                         }
5268
5269                         this.ownMethod(name, member);
5270                     }
5271                     else {
5272                         prototype[name] = member;
5273                     }
5274                 }
5275             }
5276
5277             if (enumerables) {
5278                 for (i = enumerables.length; i--;) {
5279                     name = enumerables[i];
5280
5281                     if (members.hasOwnProperty(name)) {
5282                         if (typeof prototype[name] !== 'undefined') {
5283                             previous = prototype[name];
5284                             members[name].$previous = previous;
5285                         }
5286
5287                         this.ownMethod(name, members[name]);
5288                     }
5289                 }
5290             }
5291
5292             return this;
5293         },
5294
5295         //<feature classSystem.mixins>
5296         /**
5297          * Used internally by the mixins pre-processor
5298          * @private
5299          * @inheritable
5300          */
5301         mixin: function(name, cls) {
5302             var mixin = cls.prototype,
5303                 my = this.prototype,
5304                 key, fn;
5305
5306             for (key in mixin) {
5307                 if (mixin.hasOwnProperty(key)) {
5308                     if (typeof my[key] === 'undefined' && key !== 'mixins' && key !== 'mixinId') {
5309                         if (typeof mixin[key] === 'function') {
5310                             fn = mixin[key];
5311
5312                             if (typeof fn.$owner === 'undefined') {
5313                                 this.ownMethod(key, fn);
5314                             }
5315                             else {
5316                                 my[key] = fn;
5317                             }
5318                         }
5319                         else {
5320                             my[key] = mixin[key];
5321                         }
5322                     }
5323                     //<feature classSystem.config>
5324                     else if (key === 'config' && my.config && mixin.config) {
5325                         Ext.Object.merge(my.config, mixin.config);
5326                     }
5327                     //</feature>
5328                 }
5329             }
5330
5331             if (typeof mixin.onClassMixedIn !== 'undefined') {
5332                 mixin.onClassMixedIn.call(cls, this);
5333             }
5334
5335             if (!my.hasOwnProperty('mixins')) {
5336                 if ('mixins' in my) {
5337                     my.mixins = Ext.Object.merge({}, my.mixins);
5338                 }
5339                 else {
5340                     my.mixins = {};
5341                 }
5342             }
5343
5344             my.mixins[name] = mixin;
5345         },
5346         //</feature>
5347
5348         /**
5349          * Get the current class' name in string format.
5350          *
5351          *     Ext.define('My.cool.Class', {
5352          *         constructor: function() {
5353          *             alert(this.self.getName()); // alerts 'My.cool.Class'
5354          *         }
5355          *     });
5356          *
5357          *     My.cool.Class.getName(); // 'My.cool.Class'
5358          *
5359          * @return {String} className
5360          * @static
5361          * @inheritable
5362          */
5363         getName: function() {
5364             return Ext.getClassName(this);
5365         },
5366
5367         /**
5368          * Create aliases for existing prototype methods. Example:
5369          *
5370          *     Ext.define('My.cool.Class', {
5371          *         method1: function() { ... },
5372          *         method2: function() { ... }
5373          *     });
5374          *
5375          *     var test = new My.cool.Class();
5376          *
5377          *     My.cool.Class.createAlias({
5378          *         method3: 'method1',
5379          *         method4: 'method2'
5380          *     });
5381          *
5382          *     test.method3(); // test.method1()
5383          *
5384          *     My.cool.Class.createAlias('method5', 'method3');
5385          *
5386          *     test.method5(); // test.method3() -> test.method1()
5387          *
5388          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5389          * {@link Ext.Function#flexSetter flexSetter}
5390          * @param {String/Object} origin The original method name
5391          * @static
5392          * @inheritable
5393          * @method
5394          */
5395         createAlias: flexSetter(function(alias, origin) {
5396             this.prototype[alias] = function() {
5397                 return this[origin].apply(this, arguments);
5398             }
5399         })
5400     });
5401
5402 })(Ext.Function.flexSetter);
5403
5404 /**
5405  * @author Jacky Nguyen <jacky@sencha.com>
5406  * @docauthor Jacky Nguyen <jacky@sencha.com>
5407  * @class Ext.Class
5408  *
5409  * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
5410  * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
5411  * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
5412  *
5413  * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
5414  * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
5415  *
5416  * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
5417  * from, see {@link Ext.Base}.
5418  */
5419 (function() {
5420
5421     var Class,
5422         Base = Ext.Base,
5423         baseStaticProperties = [],
5424         baseStaticProperty;
5425
5426     for (baseStaticProperty in Base) {
5427         if (Base.hasOwnProperty(baseStaticProperty)) {
5428             baseStaticProperties.push(baseStaticProperty);
5429         }
5430     }
5431
5432     /**
5433      * @method constructor
5434      * Creates new class.
5435      * @param {Object} classData An object represent the properties of this class
5436      * @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
5437      * Note that the creation process can be asynchronous depending on the pre-processors used.
5438      * @return {Ext.Base} The newly created class
5439      */
5440     Ext.Class = Class = function(newClass, classData, onClassCreated) {
5441         if (typeof newClass != 'function') {
5442             onClassCreated = classData;
5443             classData = newClass;
5444             newClass = function() {
5445                 return this.constructor.apply(this, arguments);
5446             };
5447         }
5448
5449         if (!classData) {
5450             classData = {};
5451         }
5452
5453         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5454             registeredPreprocessors = Class.getPreprocessors(),
5455             index = 0,
5456             preprocessors = [],
5457             preprocessor, staticPropertyName, process, i, j, ln;
5458
5459         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5460             staticPropertyName = baseStaticProperties[i];
5461             newClass[staticPropertyName] = Base[staticPropertyName];
5462         }
5463
5464         delete classData.preprocessors;
5465
5466         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5467             preprocessor = preprocessorStack[j];
5468
5469             if (typeof preprocessor == 'string') {
5470                 preprocessor = registeredPreprocessors[preprocessor];
5471
5472                 if (!preprocessor.always) {
5473                     if (classData.hasOwnProperty(preprocessor.name)) {
5474                         preprocessors.push(preprocessor.fn);
5475                     }
5476                 }
5477                 else {
5478                     preprocessors.push(preprocessor.fn);
5479                 }
5480             }
5481             else {
5482                 preprocessors.push(preprocessor);
5483             }
5484         }
5485
5486         classData.onClassCreated = onClassCreated || Ext.emptyFn;
5487
5488         classData.onBeforeClassCreated = function(cls, data) {
5489             onClassCreated = data.onClassCreated;
5490
5491             delete data.onBeforeClassCreated;
5492             delete data.onClassCreated;
5493
5494             cls.implement(data);
5495
5496             onClassCreated.call(cls, cls);
5497         };
5498
5499         process = function(cls, data) {
5500             preprocessor = preprocessors[index++];
5501
5502             if (!preprocessor) {
5503                 data.onBeforeClassCreated.apply(this, arguments);
5504                 return;
5505             }
5506
5507             if (preprocessor.call(this, cls, data, process) !== false) {
5508                 process.apply(this, arguments);
5509             }
5510         };
5511
5512         process.call(Class, newClass, classData);
5513
5514         return newClass;
5515     };
5516
5517     Ext.apply(Class, {
5518
5519         /** @private */
5520         preprocessors: {},
5521
5522         /**
5523          * Register a new pre-processor to be used during the class creation process
5524          *
5525          * @member Ext.Class
5526          * @param {String} name The pre-processor's name
5527          * @param {Function} fn The callback function to be executed. Typical format:
5528          *
5529          *     function(cls, data, fn) {
5530          *         // Your code here
5531          *
5532          *         // Execute this when the processing is finished.
5533          *         // Asynchronous processing is perfectly ok
5534          *         if (fn) {
5535          *             fn.call(this, cls, data);
5536          *         }
5537          *     });
5538          *
5539          * @param {Function} fn.cls The created class
5540          * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
5541          * @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
5542          * regardless of whether the processing is synchronous or aynchronous
5543          *
5544          * @return {Ext.Class} this
5545          * @static
5546          */
5547         registerPreprocessor: function(name, fn, always) {
5548             this.preprocessors[name] = {
5549                 name: name,
5550                 always: always ||  false,
5551                 fn: fn
5552             };
5553
5554             return this;
5555         },
5556
5557         /**
5558          * Retrieve a pre-processor callback function by its name, which has been registered before
5559          *
5560          * @param {String} name
5561          * @return {Function} preprocessor
5562          * @static
5563          */
5564         getPreprocessor: function(name) {
5565             return this.preprocessors[name];
5566         },
5567
5568         getPreprocessors: function() {
5569             return this.preprocessors;
5570         },
5571
5572         /**
5573          * Retrieve the array stack of default pre-processors
5574          *
5575          * @return {Function[]} defaultPreprocessors
5576          * @static
5577          */
5578         getDefaultPreprocessors: function() {
5579             return this.defaultPreprocessors || [];
5580         },
5581
5582         /**
5583          * Set the default array stack of default pre-processors
5584          *
5585          * @param {Function/Function[]} preprocessors
5586          * @return {Ext.Class} this
5587          * @static
5588          */
5589         setDefaultPreprocessors: function(preprocessors) {
5590             this.defaultPreprocessors = Ext.Array.from(preprocessors);
5591
5592             return this;
5593         },
5594
5595         /**
5596          * Inserts this pre-processor at a specific position in the stack, optionally relative to
5597          * any existing pre-processor. For example:
5598          *
5599          *     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5600          *         // Your code here
5601          *
5602          *         if (fn) {
5603          *             fn.call(this, cls, data);
5604          *         }
5605          *     }).setDefaultPreprocessorPosition('debug', 'last');
5606          *
5607          * @param {String} name The pre-processor name. Note that it needs to be registered with
5608          * {@link #registerPreprocessor registerPreprocessor} before this
5609          * @param {String} offset The insertion position. Four possible values are:
5610          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5611          * @param {String} relativeName
5612          * @return {Ext.Class} this
5613          * @static
5614          */
5615         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5616             var defaultPreprocessors = this.defaultPreprocessors,
5617                 index;
5618
5619             if (typeof offset == 'string') {
5620                 if (offset === 'first') {
5621                     defaultPreprocessors.unshift(name);
5622
5623                     return this;
5624                 }
5625                 else if (offset === 'last') {
5626                     defaultPreprocessors.push(name);
5627
5628                     return this;
5629                 }
5630
5631                 offset = (offset === 'after') ? 1 : -1;
5632             }
5633
5634             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5635
5636             if (index !== -1) {
5637                 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
5638             }
5639
5640             return this;
5641         }
5642     });
5643
5644     /**
5645      * @cfg {String} extend
5646      * The parent class that this class extends. For example:
5647      *
5648      *     Ext.define('Person', {
5649      *         say: function(text) { alert(text); }
5650      *     });
5651      *
5652      *     Ext.define('Developer', {
5653      *         extend: 'Person',
5654      *         say: function(text) { this.callParent(["print "+text]); }
5655      *     });
5656      */
5657     Class.registerPreprocessor('extend', function(cls, data) {
5658         var extend = data.extend,
5659             base = Ext.Base,
5660             basePrototype = base.prototype,
5661             prototype = function() {},
5662             parent, i, k, ln, staticName, parentStatics,
5663             parentPrototype, clsPrototype;
5664
5665         if (extend && extend !== Object) {
5666             parent = extend;
5667         }
5668         else {
5669             parent = base;
5670         }
5671
5672         parentPrototype = parent.prototype;
5673
5674         prototype.prototype = parentPrototype;
5675         clsPrototype = cls.prototype = new prototype();
5676
5677         if (!('$class' in parent)) {
5678             for (i in basePrototype) {
5679                 if (!parentPrototype[i]) {
5680                     parentPrototype[i] = basePrototype[i];
5681                 }
5682             }
5683         }
5684
5685         clsPrototype.self = cls;
5686
5687         cls.superclass = clsPrototype.superclass = parentPrototype;
5688
5689         delete data.extend;
5690
5691         //<feature classSystem.inheritableStatics>
5692         // Statics inheritance
5693         parentStatics = parentPrototype.$inheritableStatics;
5694
5695         if (parentStatics) {
5696             for (k = 0, ln = parentStatics.length; k < ln; k++) {
5697                 staticName = parentStatics[k];
5698
5699                 if (!cls.hasOwnProperty(staticName)) {
5700                     cls[staticName] = parent[staticName];
5701                 }
5702             }
5703         }
5704         //</feature>
5705
5706         //<feature classSystem.config>
5707         // Merge the parent class' config object without referencing it
5708         if (parentPrototype.config) {
5709             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5710         }
5711         else {
5712             clsPrototype.config = {};
5713         }
5714         //</feature>
5715
5716         //<feature classSystem.onClassExtended>
5717         if (clsPrototype.$onExtended) {
5718             clsPrototype.$onExtended.call(cls, cls, data);
5719         }
5720
5721         if (data.onClassExtended) {
5722             clsPrototype.$onExtended = data.onClassExtended;
5723             delete data.onClassExtended;
5724         }
5725         //</feature>
5726
5727     }, true);
5728
5729     //<feature classSystem.statics>
5730     /**
5731      * @cfg {Object} statics
5732      * List of static methods for this class. For example:
5733      *
5734      *     Ext.define('Computer', {
5735      *          statics: {
5736      *              factory: function(brand) {
5737      *                  // 'this' in static methods refer to the class itself
5738      *                  return new this(brand);
5739      *              }
5740      *          },
5741      *
5742      *          constructor: function() { ... }
5743      *     });
5744      *
5745      *     var dellComputer = Computer.factory('Dell');
5746      */
5747     Class.registerPreprocessor('statics', function(cls, data) {
5748         cls.addStatics(data.statics);
5749
5750         delete data.statics;
5751     });
5752     //</feature>
5753
5754     //<feature classSystem.inheritableStatics>
5755     /**
5756      * @cfg {Object} inheritableStatics
5757      * List of inheritable static methods for this class.
5758      * Otherwise just like {@link #statics} but subclasses inherit these methods.
5759      */
5760     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5761         cls.addInheritableStatics(data.inheritableStatics);
5762
5763         delete data.inheritableStatics;
5764     });
5765     //</feature>
5766
5767     //<feature classSystem.config>
5768     /**
5769      * @cfg {Object} config
5770      * List of configuration options with their default values, for which automatically
5771      * accessor methods are generated.  For example:
5772      *
5773      *     Ext.define('SmartPhone', {
5774      *          config: {
5775      *              hasTouchScreen: false,
5776      *              operatingSystem: 'Other',
5777      *              price: 500
5778      *          },
5779      *          constructor: function(cfg) {
5780      *              this.initConfig(cfg);
5781      *          }
5782      *     });
5783      *
5784      *     var iPhone = new SmartPhone({
5785      *          hasTouchScreen: true,
5786      *          operatingSystem: 'iOS'
5787      *     });
5788      *
5789      *     iPhone.getPrice(); // 500;
5790      *     iPhone.getOperatingSystem(); // 'iOS'
5791      *     iPhone.getHasTouchScreen(); // true;
5792      *     iPhone.hasTouchScreen(); // true
5793      */
5794     Class.registerPreprocessor('config', function(cls, data) {
5795         var prototype = cls.prototype;
5796
5797         Ext.Object.each(data.config, function(name) {
5798             var cName = name.charAt(0).toUpperCase() + name.substr(1),
5799                 pName = name,
5800                 apply = 'apply' + cName,
5801                 setter = 'set' + cName,
5802                 getter = 'get' + cName;
5803
5804             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5805                 data[apply] = function(val) {
5806                     return val;
5807                 };
5808             }
5809
5810             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5811                 data[setter] = function(val) {
5812                     var ret = this[apply].call(this, val, this[pName]);
5813
5814                     if (typeof ret != 'undefined') {
5815                         this[pName] = ret;
5816                     }
5817
5818                     return this;
5819                 };
5820             }
5821
5822             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5823                 data[getter] = function() {
5824                     return this[pName];
5825                 };
5826             }
5827         });
5828
5829         Ext.Object.merge(prototype.config, data.config);
5830         delete data.config;
5831     });
5832     //</feature>
5833
5834     //<feature classSystem.mixins>
5835     /**
5836      * @cfg {Object} mixins
5837      * List of classes to mix into this class. For example:
5838      *
5839      *     Ext.define('CanSing', {
5840      *          sing: function() {
5841      *              alert("I'm on the highway to hell...")
5842      *          }
5843      *     });
5844      *
5845      *     Ext.define('Musician', {
5846      *          extend: 'Person',
5847      *
5848      *          mixins: {
5849      *              canSing: 'CanSing'
5850      *          }
5851      *     })
5852      */
5853     Class.registerPreprocessor('mixins', function(cls, data) {
5854         var mixins = data.mixins,
5855             name, mixin, i, ln;
5856
5857         delete data.mixins;
5858
5859         Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
5860             if (mixins instanceof Array) {
5861                 for (i = 0,ln = mixins.length; i < ln; i++) {
5862                     mixin = mixins[i];
5863                     name = mixin.prototype.mixinId || mixin.$className;
5864
5865                     cls.mixin(name, mixin);
5866                 }
5867             }
5868             else {
5869                 for (name in mixins) {
5870                     if (mixins.hasOwnProperty(name)) {
5871                         cls.mixin(name, mixins[name]);
5872                     }
5873                 }
5874             }
5875         });
5876     });
5877
5878     //</feature>
5879
5880     Class.setDefaultPreprocessors([
5881         'extend'
5882         //<feature classSystem.statics>
5883         ,'statics'
5884         //</feature>
5885         //<feature classSystem.inheritableStatics>
5886         ,'inheritableStatics'
5887         //</feature>
5888         //<feature classSystem.config>
5889         ,'config'
5890         //</feature>
5891         //<feature classSystem.mixins>
5892         ,'mixins'
5893         //</feature>
5894     ]);
5895
5896     //<feature classSystem.backwardsCompatible>
5897     // Backwards compatible
5898     Ext.extend = function(subclass, superclass, members) {
5899         if (arguments.length === 2 && Ext.isObject(superclass)) {
5900             members = superclass;
5901             superclass = subclass;
5902             subclass = null;
5903         }
5904
5905         var cls;
5906
5907         if (!superclass) {
5908             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5909         }
5910
5911         members.extend = superclass;
5912         members.preprocessors = [
5913             'extend'
5914             //<feature classSystem.statics>
5915             ,'statics'
5916             //</feature>
5917             //<feature classSystem.inheritableStatics>
5918             ,'inheritableStatics'
5919             //</feature>
5920             //<feature classSystem.mixins>
5921             ,'mixins'
5922             //</feature>
5923             //<feature classSystem.config>
5924             ,'config'
5925             //</feature>
5926         ];
5927
5928         if (subclass) {
5929             cls = new Class(subclass, members);
5930         }
5931         else {
5932             cls = new Class(members);
5933         }
5934
5935         cls.prototype.override = function(o) {
5936             for (var m in o) {
5937                 if (o.hasOwnProperty(m)) {
5938                     this[m] = o[m];
5939                 }
5940             }
5941         };
5942
5943         return cls;
5944     };
5945     //</feature>
5946
5947 })();
5948
5949 /**
5950  * @author Jacky Nguyen <jacky@sencha.com>
5951  * @docauthor Jacky Nguyen <jacky@sencha.com>
5952  * @class Ext.ClassManager
5953  *
5954  * Ext.ClassManager manages all classes and handles mapping from string class name to
5955  * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5956  * these convenient shorthands:
5957  *
5958  * - {@link Ext#define Ext.define}
5959  * - {@link Ext#create Ext.create}
5960  * - {@link Ext#widget Ext.widget}
5961  * - {@link Ext#getClass Ext.getClass}
5962  * - {@link Ext#getClassName Ext.getClassName}
5963  *
5964  * # Basic syntax:
5965  *
5966  *     Ext.define(className, properties);
5967  *
5968  * in which `properties` is an object represent a collection of properties that apply to the class. See
5969  * {@link Ext.ClassManager#create} for more detailed instructions.
5970  *
5971  *     Ext.define('Person', {
5972  *          name: 'Unknown',
5973  *
5974  *          constructor: function(name) {
5975  *              if (name) {
5976  *                  this.name = name;
5977  *              }
5978  *
5979  *              return this;
5980  *          },
5981  *
5982  *          eat: function(foodType) {
5983  *              alert("I'm eating: " + foodType);
5984  *
5985  *              return this;
5986  *          }
5987  *     });
5988  *
5989  *     var aaron = new Person("Aaron");
5990  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5991  *
5992  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5993  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5994  *
5995  * # Inheritance:
5996  *
5997  *     Ext.define('Developer', {
5998  *          extend: 'Person',
5999  *
6000  *          constructor: function(name, isGeek) {
6001  *              this.isGeek = isGeek;
6002  *
6003  *              // Apply a method from the parent class' prototype
6004  *              this.callParent([name]);
6005  *
6006  *              return this;
6007  *
6008  *          },
6009  *
6010  *          code: function(language) {
6011  *              alert("I'm coding in: " + language);
6012  *
6013  *              this.eat("Bugs");
6014  *
6015  *              return this;
6016  *          }
6017  *     });
6018  *
6019  *     var jacky = new Developer("Jacky", true);
6020  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
6021  *                               // alert("I'm eating: Bugs");
6022  *
6023  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
6024  *
6025  * # Mixins:
6026  *
6027  *     Ext.define('CanPlayGuitar', {
6028  *          playGuitar: function() {
6029  *             alert("F#...G...D...A");
6030  *          }
6031  *     });
6032  *
6033  *     Ext.define('CanComposeSongs', {
6034  *          composeSongs: function() { ... }
6035  *     });
6036  *
6037  *     Ext.define('CanSing', {
6038  *          sing: function() {
6039  *              alert("I'm on the highway to hell...")
6040  *          }
6041  *     });
6042  *
6043  *     Ext.define('Musician', {
6044  *          extend: 'Person',
6045  *
6046  *          mixins: {
6047  *              canPlayGuitar: 'CanPlayGuitar',
6048  *              canComposeSongs: 'CanComposeSongs',
6049  *              canSing: 'CanSing'
6050  *          }
6051  *     })
6052  *
6053  *     Ext.define('CoolPerson', {
6054  *          extend: 'Person',
6055  *
6056  *          mixins: {
6057  *              canPlayGuitar: 'CanPlayGuitar',
6058  *              canSing: 'CanSing'
6059  *          },
6060  *
6061  *          sing: function() {
6062  *              alert("Ahem....");
6063  *
6064  *              this.mixins.canSing.sing.call(this);
6065  *
6066  *              alert("[Playing guitar at the same time...]");
6067  *
6068  *              this.playGuitar();
6069  *          }
6070  *     });
6071  *
6072  *     var me = new CoolPerson("Jacky");
6073  *
6074  *     me.sing(); // alert("Ahem...");
6075  *                // alert("I'm on the highway to hell...");
6076  *                // alert("[Playing guitar at the same time...]");
6077  *                // alert("F#...G...D...A");
6078  *
6079  * # Config:
6080  *
6081  *     Ext.define('SmartPhone', {
6082  *          config: {
6083  *              hasTouchScreen: false,
6084  *              operatingSystem: 'Other',
6085  *              price: 500
6086  *          },
6087  *
6088  *          isExpensive: false,
6089  *
6090  *          constructor: function(config) {
6091  *              this.initConfig(config);
6092  *
6093  *              return this;
6094  *          },
6095  *
6096  *          applyPrice: function(price) {
6097  *              this.isExpensive = (price > 500);
6098  *
6099  *              return price;
6100  *          },
6101  *
6102  *          applyOperatingSystem: function(operatingSystem) {
6103  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
6104  *                  return 'Other';
6105  *              }
6106  *
6107  *              return operatingSystem;
6108  *          }
6109  *     });
6110  *
6111  *     var iPhone = new SmartPhone({
6112  *          hasTouchScreen: true,
6113  *          operatingSystem: 'iOS'
6114  *     });
6115  *
6116  *     iPhone.getPrice(); // 500;
6117  *     iPhone.getOperatingSystem(); // 'iOS'
6118  *     iPhone.getHasTouchScreen(); // true;
6119  *     iPhone.hasTouchScreen(); // true
6120  *
6121  *     iPhone.isExpensive; // false;
6122  *     iPhone.setPrice(600);
6123  *     iPhone.getPrice(); // 600
6124  *     iPhone.isExpensive; // true;
6125  *
6126  *     iPhone.setOperatingSystem('AlienOS');
6127  *     iPhone.getOperatingSystem(); // 'Other'
6128  *
6129  * # Statics:
6130  *
6131  *     Ext.define('Computer', {
6132  *          statics: {
6133  *              factory: function(brand) {
6134  *                 // 'this' in static methods refer to the class itself
6135  *                  return new this(brand);
6136  *              }
6137  *          },
6138  *
6139  *          constructor: function() { ... }
6140  *     });
6141  *
6142  *     var dellComputer = Computer.factory('Dell');
6143  *
6144  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
6145  * static properties within class methods
6146  *
6147  * @singleton
6148  */
6149 (function(Class, alias) {
6150
6151     var slice = Array.prototype.slice;
6152
6153     var Manager = Ext.ClassManager = {
6154
6155         /**
6156          * @property {Object} classes
6157          * All classes which were defined through the ClassManager. Keys are the
6158          * name of the classes and the values are references to the classes.
6159          * @private
6160          */
6161         classes: {},
6162
6163         /**
6164          * @private
6165          */
6166         existCache: {},
6167
6168         /**
6169          * @private
6170          */
6171         namespaceRewrites: [{
6172             from: 'Ext.',
6173             to: Ext
6174         }],
6175
6176         /**
6177          * @private
6178          */
6179         maps: {
6180             alternateToName: {},
6181             aliasToName: {},
6182             nameToAliases: {}
6183         },
6184
6185         /** @private */
6186         enableNamespaceParseCache: true,
6187
6188         /** @private */
6189         namespaceParseCache: {},
6190
6191         /** @private */
6192         instantiators: [],
6193
6194         /** @private */
6195         instantiationCounts: {},
6196
6197         /**
6198          * Checks if a class has already been created.
6199          *
6200          * @param {String} className
6201          * @return {Boolean} exist
6202          */
6203         isCreated: function(className) {
6204             var i, ln, part, root, parts;
6205
6206             if (typeof className !== 'string' || className.length < 1) {
6207                 Ext.Error.raise({
6208                     sourceClass: "Ext.ClassManager",
6209                     sourceMethod: "exist",
6210                     msg: "Invalid classname, must be a string and must not be empty"
6211                 });
6212             }
6213
6214             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
6215                 return true;
6216             }
6217
6218             root = Ext.global;
6219             parts = this.parseNamespace(className);
6220
6221             for (i = 0, ln = parts.length; i < ln; i++) {
6222                 part = parts[i];
6223
6224                 if (typeof part !== 'string') {
6225                     root = part;
6226                 } else {
6227                     if (!root || !root[part]) {
6228                         return false;
6229                     }
6230
6231                     root = root[part];
6232                 }
6233             }
6234
6235             Ext.Loader.historyPush(className);
6236
6237             this.existCache[className] = true;
6238
6239             return true;
6240         },
6241
6242         /**
6243          * Supports namespace rewriting
6244          * @private
6245          */
6246         parseNamespace: function(namespace) {
6247             if (typeof namespace !== 'string') {
6248                 Ext.Error.raise({
6249                     sourceClass: "Ext.ClassManager",
6250                     sourceMethod: "parseNamespace",
6251                     msg: "Invalid namespace, must be a string"
6252                 });
6253             }
6254
6255             var cache = this.namespaceParseCache;
6256
6257             if (this.enableNamespaceParseCache) {
6258                 if (cache.hasOwnProperty(namespace)) {
6259                     return cache[namespace];
6260                 }
6261             }
6262
6263             var parts = [],
6264                 rewrites = this.namespaceRewrites,
6265                 rewrite, from, to, i, ln, root = Ext.global;
6266
6267             for (i = 0, ln = rewrites.length; i < ln; i++) {
6268                 rewrite = rewrites[i];
6269                 from = rewrite.from;
6270                 to = rewrite.to;
6271
6272                 if (namespace === from || namespace.substring(0, from.length) === from) {
6273                     namespace = namespace.substring(from.length);
6274
6275                     if (typeof to !== 'string') {
6276                         root = to;
6277                     } else {
6278                         parts = parts.concat(to.split('.'));
6279                     }
6280
6281                     break;
6282                 }
6283             }
6284
6285             parts.push(root);
6286
6287             parts = parts.concat(namespace.split('.'));
6288
6289             if (this.enableNamespaceParseCache) {
6290                 cache[namespace] = parts;
6291             }
6292
6293             return parts;
6294         },
6295
6296         /**
6297          * Creates a namespace and assign the `value` to the created object
6298          *
6299          *     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
6300          *
6301          *     alert(MyCompany.pkg.Example === someObject); // alerts true
6302          *
6303          * @param {String} name
6304          * @param {Object} value
6305          */
6306         setNamespace: function(name, value) {
6307             var root = Ext.global,
6308                 parts = this.parseNamespace(name),
6309                 ln = parts.length - 1,
6310                 leaf = parts[ln],
6311                 i, part;
6312
6313             for (i = 0; i < ln; i++) {
6314                 part = parts[i];
6315
6316                 if (typeof part !== 'string') {
6317                     root = part;
6318                 } else {
6319                     if (!root[part]) {
6320                         root[part] = {};
6321                     }
6322
6323                     root = root[part];
6324                 }
6325             }
6326
6327             root[leaf] = value;
6328
6329             return root[leaf];
6330         },
6331
6332         /**
6333          * The new Ext.ns, supports namespace rewriting
6334          * @private
6335          */
6336         createNamespaces: function() {
6337             var root = Ext.global,
6338                 parts, part, i, j, ln, subLn;
6339
6340             for (i = 0, ln = arguments.length; i < ln; i++) {
6341                 parts = this.parseNamespace(arguments[i]);
6342
6343                 for (j = 0, subLn = parts.length; j < subLn; j++) {
6344                     part = parts[j];
6345
6346                     if (typeof part !== 'string') {
6347                         root = part;
6348                     } else {
6349                         if (!root[part]) {
6350                             root[part] = {};
6351                         }
6352
6353                         root = root[part];
6354                     }
6355                 }
6356             }
6357
6358             return root;
6359         },
6360
6361         /**
6362          * Sets a name reference to a class.
6363          *
6364          * @param {String} name
6365          * @param {Object} value
6366          * @return {Ext.ClassManager} this
6367          */
6368         set: function(name, value) {
6369             var targetName = this.getName(value);
6370
6371             this.classes[name] = this.setNamespace(name, value);
6372
6373             if (targetName && targetName !== name) {
6374                 this.maps.alternateToName[name] = targetName;
6375             }
6376
6377             return this;
6378         },
6379
6380         /**
6381          * Retrieve a class by its name.
6382          *
6383          * @param {String} name
6384          * @return {Ext.Class} class
6385          */
6386         get: function(name) {
6387             if (this.classes.hasOwnProperty(name)) {
6388                 return this.classes[name];
6389             }
6390
6391             var root = Ext.global,
6392                 parts = this.parseNamespace(name),
6393                 part, i, ln;
6394
6395             for (i = 0, ln = parts.length; i < ln; i++) {
6396                 part = parts[i];
6397
6398                 if (typeof part !== 'string') {
6399                     root = part;
6400                 } else {
6401                     if (!root || !root[part]) {
6402                         return null;
6403                     }
6404
6405                     root = root[part];
6406                 }
6407             }
6408
6409             return root;
6410         },
6411
6412         /**
6413          * Register the alias for a class.
6414          *
6415          * @param {Ext.Class/String} cls a reference to a class or a className
6416          * @param {String} alias Alias to use when referring to this class
6417          */
6418         setAlias: function(cls, alias) {
6419             var aliasToNameMap = this.maps.aliasToName,
6420                 nameToAliasesMap = this.maps.nameToAliases,
6421                 className;
6422
6423             if (typeof cls === 'string') {
6424                 className = cls;
6425             } else {
6426                 className = this.getName(cls);
6427             }
6428
6429             if (alias && aliasToNameMap[alias] !== className) {
6430                 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
6431                     Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
6432                         "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
6433                 }
6434
6435                 aliasToNameMap[alias] = className;
6436             }
6437
6438             if (!nameToAliasesMap[className]) {
6439                 nameToAliasesMap[className] = [];
6440             }
6441
6442             if (alias) {
6443                 Ext.Array.include(nameToAliasesMap[className], alias);
6444             }
6445
6446             return this;
6447         },
6448
6449         /**
6450          * Get a reference to the class by its alias.
6451          *
6452          * @param {String} alias
6453          * @return {Ext.Class} class
6454          */
6455         getByAlias: function(alias) {
6456             return this.get(this.getNameByAlias(alias));
6457         },
6458
6459         /**
6460          * Get the name of a class by its alias.
6461          *
6462          * @param {String} alias
6463          * @return {String} className
6464          */
6465         getNameByAlias: function(alias) {
6466             return this.maps.aliasToName[alias] || '';
6467         },
6468
6469         /**
6470          * Get the name of a class by its alternate name.
6471          *
6472          * @param {String} alternate
6473          * @return {String} className
6474          */
6475         getNameByAlternate: function(alternate) {
6476             return this.maps.alternateToName[alternate] || '';
6477         },
6478
6479         /**
6480          * Get the aliases of a class by the class name
6481          *
6482          * @param {String} name
6483          * @return {String[]} aliases
6484          */
6485         getAliasesByName: function(name) {
6486             return this.maps.nameToAliases[name] || [];
6487         },
6488
6489         /**
6490          * Get the name of the class by its reference or its instance.
6491          *
6492          *     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
6493          *
6494          * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
6495          *
6496          * @param {Ext.Class/Object} object
6497          * @return {String} className
6498          */
6499         getName: function(object) {
6500             return object && object.$className || '';
6501         },
6502
6503         /**
6504          * Get the class of the provided object; returns null if it's not an instance
6505          * of any class created with Ext.define.
6506          *
6507          *     var component = new Ext.Component();
6508          *
6509          *     Ext.ClassManager.getClass(component); // returns Ext.Component
6510          *
6511          * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
6512          *
6513          * @param {Object} object
6514          * @return {Ext.Class} class
6515          */
6516         getClass: function(object) {
6517             return object && object.self || null;
6518         },
6519
6520         /**
6521          * Defines a class.
6522          *
6523          * {@link Ext#define Ext.define} and {@link Ext.ClassManager#create Ext.ClassManager.create} are almost aliases
6524          * of each other, with the only exception that Ext.define allows definition of {@link Ext.Class#override overrides}.
6525          * To avoid trouble, always use Ext.define.
6526          *
6527          *     Ext.define('My.awesome.Class', {
6528          *         someProperty: 'something',
6529          *         someMethod: function() { ... }
6530          *         ...
6531          *
6532          *     }, function() {
6533          *         alert('Created!');
6534          *         alert(this === My.awesome.Class); // alerts true
6535          *
6536          *         var myInstance = new this();
6537          *     });
6538          *
6539          * @param {String} className The class name to create in string dot-namespaced format, for example:
6540          * `My.very.awesome.Class`, `FeedViewer.plugin.CoolPager`. It is highly recommended to follow this simple convention:
6541          *
6542          * - The root and the class name are 'CamelCased'
6543          * - Everything else is lower-cased
6544          *
6545          * @param {Object} data The key-value pairs of properties to apply to this class. Property names can be of any valid
6546          * strings, except those in the reserved list below:
6547          *
6548          * - {@link Ext.Base#self self}
6549          * - {@link Ext.Class#alias alias}
6550          * - {@link Ext.Class#alternateClassName alternateClassName}
6551          * - {@link Ext.Class#config config}
6552          * - {@link Ext.Class#extend extend}
6553          * - {@link Ext.Class#inheritableStatics inheritableStatics}
6554          * - {@link Ext.Class#mixins mixins}
6555          * - {@link Ext.Class#override override} (only when using {@link Ext#define Ext.define})
6556          * - {@link Ext.Class#requires requires}
6557          * - {@link Ext.Class#singleton singleton}
6558          * - {@link Ext.Class#statics statics}
6559          * - {@link Ext.Class#uses uses}
6560          *
6561          * @param {Function} [createdFn] callback to execute after the class is created, the execution scope of which
6562          * (`this`) will be the newly created class itself.
6563          *
6564          * @return {Ext.Base}
6565          */
6566         create: function(className, data, createdFn) {
6567             var manager = this;
6568
6569             if (typeof className !== 'string') {
6570                 Ext.Error.raise({
6571                     sourceClass: "Ext",
6572                     sourceMethod: "define",
6573                     msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6574                 });
6575             }
6576
6577             data.$className = className;
6578
6579             return new Class(data, function() {
6580                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6581                     registeredPostprocessors = manager.postprocessors,
6582                     index = 0,
6583                     postprocessors = [],
6584                     postprocessor, process, i, ln;
6585
6586                 delete data.postprocessors;
6587
6588                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6589                     postprocessor = postprocessorStack[i];
6590
6591                     if (typeof postprocessor === 'string') {
6592                         postprocessor = registeredPostprocessors[postprocessor];
6593
6594                         if (!postprocessor.always) {
6595                             if (data[postprocessor.name] !== undefined) {
6596                                 postprocessors.push(postprocessor.fn);
6597                             }
6598                         }
6599                         else {
6600                             postprocessors.push(postprocessor.fn);
6601                         }
6602                     }
6603                     else {
6604                         postprocessors.push(postprocessor);
6605                     }
6606                 }
6607
6608                 process = function(clsName, cls, clsData) {
6609                     postprocessor = postprocessors[index++];
6610
6611                     if (!postprocessor) {
6612                         manager.set(className, cls);
6613
6614                         Ext.Loader.historyPush(className);
6615
6616                         if (createdFn) {
6617                             createdFn.call(cls, cls);
6618                         }
6619
6620                         return;
6621                     }
6622
6623                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6624                         process.apply(this, arguments);
6625                     }
6626                 };
6627
6628                 process.call(manager, className, this, data);
6629             });
6630         },
6631
6632         /**
6633          * Instantiate a class by its alias.
6634          *
6635          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6636          * attempt to load the class via synchronous loading.
6637          *
6638          *     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6639          *
6640          * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
6641          *
6642          * @param {String} alias
6643          * @param {Object...} args Additional arguments after the alias will be passed to the
6644          * class constructor.
6645          * @return {Object} instance
6646          */
6647         instantiateByAlias: function() {
6648             var alias = arguments[0],
6649                 args = slice.call(arguments),
6650                 className = this.getNameByAlias(alias);
6651
6652             if (!className) {
6653                 className = this.maps.aliasToName[alias];
6654
6655                 if (!className) {
6656                     Ext.Error.raise({
6657                         sourceClass: "Ext",
6658                         sourceMethod: "createByAlias",
6659                         msg: "Cannot create an instance of unrecognized alias: " + alias
6660                     });
6661                 }
6662
6663                 if (Ext.global.console) {
6664                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6665                          "Ext.require('" + alias + "') above Ext.onReady");
6666                 }
6667
6668                 Ext.syncRequire(className);
6669             }
6670
6671             args[0] = className;
6672
6673             return this.instantiate.apply(this, args);
6674         },
6675
6676         /**
6677          * Instantiate a class by either full name, alias or alternate name.
6678          *
6679          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6680          * attempt to load the class via synchronous loading.
6681          *
6682          * For example, all these three lines return the same result:
6683          *
6684          *     // alias
6685          *     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6686          *
6687          *     // alternate name
6688          *     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6689          *
6690          *     // full class name
6691          *     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6692          *
6693          * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
6694          *
6695          * @param {String} name
6696          * @param {Object...} args Additional arguments after the name will be passed to the class' constructor.
6697          * @return {Object} instance
6698          */
6699         instantiate: function() {
6700             var name = arguments[0],
6701                 args = slice.call(arguments, 1),
6702                 alias = name,
6703                 possibleName, cls;
6704
6705             if (typeof name !== 'function') {
6706                 if ((typeof name !== 'string' || name.length < 1)) {
6707                     Ext.Error.raise({
6708                         sourceClass: "Ext",
6709                         sourceMethod: "create",
6710                         msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6711                     });
6712                 }
6713
6714                 cls = this.get(name);
6715             }
6716             else {
6717                 cls = name;
6718             }
6719
6720             // No record of this class name, it's possibly an alias, so look it up
6721             if (!cls) {
6722                 possibleName = this.getNameByAlias(name);
6723
6724                 if (possibleName) {
6725                     name = possibleName;
6726
6727                     cls = this.get(name);
6728                 }
6729             }
6730
6731             // Still no record of this class name, it's possibly an alternate name, so look it up
6732             if (!cls) {
6733                 possibleName = this.getNameByAlternate(name);
6734
6735                 if (possibleName) {
6736                     name = possibleName;
6737
6738                     cls = this.get(name);
6739                 }
6740             }
6741
6742             // Still not existing at this point, try to load it via synchronous mode as the last resort
6743             if (!cls) {
6744                 if (Ext.global.console) {
6745                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6746                          "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6747                 }
6748
6749                 Ext.syncRequire(name);
6750
6751                 cls = this.get(name);
6752             }
6753
6754             if (!cls) {
6755                 Ext.Error.raise({
6756                     sourceClass: "Ext",
6757                     sourceMethod: "create",
6758                     msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6759                 });
6760             }
6761
6762             if (typeof cls !== 'function') {
6763                 Ext.Error.raise({
6764                     sourceClass: "Ext",
6765                     sourceMethod: "create",
6766                     msg: "'" + name + "' is a singleton and cannot be instantiated"
6767                 });
6768             }
6769
6770             if (!this.instantiationCounts[name]) {
6771                 this.instantiationCounts[name] = 0;
6772             }
6773
6774             this.instantiationCounts[name]++;
6775
6776             return this.getInstantiator(args.length)(cls, args);
6777         },
6778
6779         /**
6780          * @private
6781          * @param name
6782          * @param args
6783          */
6784         dynInstantiate: function(name, args) {
6785             args = Ext.Array.from(args, true);
6786             args.unshift(name);
6787
6788             return this.instantiate.apply(this, args);
6789         },
6790
6791         /**
6792          * @private
6793          * @param length
6794          */
6795         getInstantiator: function(length) {
6796             if (!this.instantiators[length]) {
6797                 var i = length,
6798                     args = [];
6799
6800                 for (i = 0; i < length; i++) {
6801                     args.push('a['+i+']');
6802                 }
6803
6804                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6805             }
6806
6807             return this.instantiators[length];
6808         },
6809
6810         /**
6811          * @private
6812          */
6813         postprocessors: {},
6814
6815         /**
6816          * @private
6817          */
6818         defaultPostprocessors: [],
6819
6820         /**
6821          * Register a post-processor function.
6822          *
6823          * @param {String} name
6824          * @param {Function} postprocessor
6825          */
6826         registerPostprocessor: function(name, fn, always) {
6827             this.postprocessors[name] = {
6828                 name: name,
6829                 always: always ||  false,
6830                 fn: fn
6831             };
6832
6833             return this;
6834         },
6835
6836         /**
6837          * Set the default post processors array stack which are applied to every class.
6838          *
6839          * @param {String/String[]} The name of a registered post processor or an array of registered names.
6840          * @return {Ext.ClassManager} this
6841          */
6842         setDefaultPostprocessors: function(postprocessors) {
6843             this.defaultPostprocessors = Ext.Array.from(postprocessors);
6844
6845             return this;
6846         },
6847
6848         /**
6849          * Insert this post-processor at a specific position in the stack, optionally relative to
6850          * any existing post-processor
6851          *
6852          * @param {String} name The post-processor name. Note that it needs to be registered with
6853          * {@link Ext.ClassManager#registerPostprocessor} before this
6854          * @param {String} offset The insertion position. Four possible values are:
6855          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6856          * @param {String} relativeName
6857          * @return {Ext.ClassManager} this
6858          */
6859         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6860             var defaultPostprocessors = this.defaultPostprocessors,
6861                 index;
6862
6863             if (typeof offset === 'string') {
6864                 if (offset === 'first') {
6865                     defaultPostprocessors.unshift(name);
6866
6867                     return this;
6868                 }
6869                 else if (offset === 'last') {
6870                     defaultPostprocessors.push(name);
6871
6872                     return this;
6873                 }
6874
6875                 offset = (offset === 'after') ? 1 : -1;
6876             }
6877
6878             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6879
6880             if (index !== -1) {
6881                 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
6882             }
6883
6884             return this;
6885         },
6886
6887         /**
6888          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6889          * or class names. Expressions support wildcards:
6890          *
6891          *     // returns ['Ext.window.Window']
6892          *     var window = Ext.ClassManager.getNamesByExpression('widget.window');
6893          *
6894          *     // returns ['widget.panel', 'widget.window', ...]
6895          *     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6896          *
6897          *     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6898          *     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6899          *
6900          * @param {String} expression
6901          * @return {String[]} classNames
6902          */
6903         getNamesByExpression: function(expression) {
6904             var nameToAliasesMap = this.maps.nameToAliases,
6905                 names = [],
6906                 name, alias, aliases, possibleName, regex, i, ln;
6907
6908             if (typeof expression !== 'string' || expression.length < 1) {
6909                 Ext.Error.raise({
6910                     sourceClass: "Ext.ClassManager",
6911                     sourceMethod: "getNamesByExpression",
6912                     msg: "Expression " + expression + " is invalid, must be a non-empty string"
6913                 });
6914             }
6915
6916             if (expression.indexOf('*') !== -1) {
6917                 expression = expression.replace(/\*/g, '(.*?)');
6918                 regex = new RegExp('^' + expression + '$');
6919
6920                 for (name in nameToAliasesMap) {
6921                     if (nameToAliasesMap.hasOwnProperty(name)) {
6922                         aliases = nameToAliasesMap[name];
6923
6924                         if (name.search(regex) !== -1) {
6925                             names.push(name);
6926                         }
6927                         else {
6928                             for (i = 0, ln = aliases.length; i < ln; i++) {
6929                                 alias = aliases[i];
6930
6931                                 if (alias.search(regex) !== -1) {
6932                                     names.push(name);
6933                                     break;
6934                                 }
6935                             }
6936                         }
6937                     }
6938                 }
6939
6940             } else {
6941                 possibleName = this.getNameByAlias(expression);
6942
6943                 if (possibleName) {
6944                     names.push(possibleName);
6945                 } else {
6946                     possibleName = this.getNameByAlternate(expression);
6947
6948                     if (possibleName) {
6949                         names.push(possibleName);
6950                     } else {
6951                         names.push(expression);
6952                     }
6953                 }
6954             }
6955
6956             return names;
6957         }
6958     };
6959
6960     var defaultPostprocessors = Manager.defaultPostprocessors;
6961     //<feature classSystem.alias>
6962
6963     /**
6964      * @cfg {String[]} alias
6965      * @member Ext.Class
6966      * List of short aliases for class names.  Most useful for defining xtypes for widgets:
6967      *
6968      *     Ext.define('MyApp.CoolPanel', {
6969      *         extend: 'Ext.panel.Panel',
6970      *         alias: ['widget.coolpanel'],
6971      *         title: 'Yeah!'
6972      *     });
6973      *
6974      *     // Using Ext.create
6975      *     Ext.widget('widget.coolpanel');
6976      *     // Using the shorthand for widgets and in xtypes
6977      *     Ext.widget('panel', {
6978      *         items: [
6979      *             {xtype: 'coolpanel', html: 'Foo'},
6980      *             {xtype: 'coolpanel', html: 'Bar'}
6981      *         ]
6982      *     });
6983      */
6984     Manager.registerPostprocessor('alias', function(name, cls, data) {
6985         var aliases = data.alias,
6986             i, ln;
6987
6988         delete data.alias;
6989
6990         for (i = 0, ln = aliases.length; i < ln; i++) {
6991             alias = aliases[i];
6992
6993             this.setAlias(cls, alias);
6994         }
6995     });
6996
6997     /**
6998      * @cfg {Boolean} singleton
6999      * @member Ext.Class
7000      * When set to true, the class will be instantiated as singleton.  For example:
7001      *
7002      *     Ext.define('Logger', {
7003      *         singleton: true,
7004      *         log: function(msg) {
7005      *             console.log(msg);
7006      *         }
7007      *     });
7008      *
7009      *     Logger.log('Hello');
7010      */
7011     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
7012         fn.call(this, name, new cls(), data);
7013         return false;
7014     });
7015
7016     /**
7017      * @cfg {String/String[]} alternateClassName
7018      * @member Ext.Class
7019      * Defines alternate names for this class.  For example:
7020      *
7021      *     Ext.define('Developer', {
7022      *         alternateClassName: ['Coder', 'Hacker'],
7023      *         code: function(msg) {
7024      *             alert('Typing... ' + msg);
7025      *         }
7026      *     });
7027      *
7028      *     var joe = Ext.create('Developer');
7029      *     joe.code('stackoverflow');
7030      *
7031      *     var rms = Ext.create('Hacker');
7032      *     rms.code('hack hack');
7033      */
7034     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
7035         var alternates = data.alternateClassName,
7036             i, ln, alternate;
7037
7038         if (!(alternates instanceof Array)) {
7039             alternates = [alternates];
7040         }
7041
7042         for (i = 0, ln = alternates.length; i < ln; i++) {
7043             alternate = alternates[i];
7044
7045             if (typeof alternate !== 'string') {
7046                 Ext.Error.raise({
7047                     sourceClass: "Ext",
7048                     sourceMethod: "define",
7049                     msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
7050                 });
7051             }
7052
7053             this.set(alternate, cls);
7054         }
7055     });
7056
7057     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
7058
7059     Ext.apply(Ext, {
7060         /**
7061          * @method
7062          * @member Ext
7063          * @alias Ext.ClassManager#instantiate
7064          */
7065         create: alias(Manager, 'instantiate'),
7066
7067         /**
7068          * @private
7069          * API to be stablized
7070          *
7071          * @param {Object} item
7072          * @param {String} namespace
7073          */
7074         factory: function(item, namespace) {
7075             if (item instanceof Array) {
7076                 var i, ln;
7077
7078                 for (i = 0, ln = item.length; i < ln; i++) {
7079                     item[i] = Ext.factory(item[i], namespace);
7080                 }
7081
7082                 return item;
7083             }
7084
7085             var isString = (typeof item === 'string');
7086
7087             if (isString || (item instanceof Object && item.constructor === Object)) {
7088                 var name, config = {};
7089
7090                 if (isString) {
7091                     name = item;
7092                 }
7093                 else {
7094                     name = item.className;
7095                     config = item;
7096                     delete config.className;
7097                 }
7098
7099                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
7100                     name = namespace + '.' + Ext.String.capitalize(name);
7101                 }
7102
7103                 return Ext.create(name, config);
7104             }
7105
7106             if (typeof item === 'function') {
7107                 return Ext.create(item);
7108             }
7109
7110             return item;
7111         },
7112
7113         /**
7114          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
7115          *
7116          *     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
7117          *     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
7118          *
7119          * @method
7120          * @member Ext
7121          * @param {String} name  xtype of the widget to create.
7122          * @param {Object...} args  arguments for the widget constructor.
7123          * @return {Object} widget instance
7124          */
7125         widget: function(name) {
7126             var args = slice.call(arguments);
7127             args[0] = 'widget.' + name;
7128
7129             return Manager.instantiateByAlias.apply(Manager, args);
7130         },
7131
7132         /**
7133          * @method
7134          * @member Ext
7135          * @alias Ext.ClassManager#instantiateByAlias
7136          */
7137         createByAlias: alias(Manager, 'instantiateByAlias'),
7138
7139         /**
7140          * @cfg {String} override
7141          * @member Ext.Class
7142          * 
7143          * Defines an override applied to a class. Note that **overrides can only be created using
7144          * {@link Ext#define}.** {@link Ext.ClassManager#create} only creates classes.
7145          * 
7146          * To define an override, include the override property. The content of an override is
7147          * aggregated with the specified class in order to extend or modify that class. This can be
7148          * as simple as setting default property values or it can extend and/or replace methods.
7149          * This can also extend the statics of the class.
7150          *
7151          * One use for an override is to break a large class into manageable pieces.
7152          *
7153          *      // File: /src/app/Panel.js
7154          *
7155          *      Ext.define('My.app.Panel', {
7156          *          extend: 'Ext.panel.Panel',
7157          *          requires: [
7158          *              'My.app.PanelPart2',
7159          *              'My.app.PanelPart3'
7160          *          ]
7161          *
7162          *          constructor: function (config) {
7163          *              this.callSuper(arguments); // calls Ext.panel.Panel's constructor
7164          *              //...
7165          *          },
7166          *
7167          *          statics: {
7168          *              method: function () {
7169          *                  return 'abc';
7170          *              }
7171          *          }
7172          *      });
7173          *
7174          *      // File: /src/app/PanelPart2.js
7175          *      Ext.define('My.app.PanelPart2', {
7176          *          override: 'My.app.Panel',
7177          *
7178          *          constructor: function (config) {
7179          *              this.callSuper(arguments); // calls My.app.Panel's constructor
7180          *              //...
7181          *          }
7182          *      });
7183          *
7184          * Another use of overrides is to provide optional parts of classes that can be
7185          * independently required. In this case, the class may even be unaware of the
7186          * override altogether.
7187          *
7188          *      Ext.define('My.ux.CoolTip', {
7189          *          override: 'Ext.tip.ToolTip',
7190          *
7191          *          constructor: function (config) {
7192          *              this.callSuper(arguments); // calls Ext.tip.ToolTip's constructor
7193          *              //...
7194          *          }
7195          *      });
7196          *
7197          * The above override can now be required as normal.
7198          *
7199          *      Ext.define('My.app.App', {
7200          *          requires: [
7201          *              'My.ux.CoolTip'
7202          *          ]
7203          *      });
7204          *
7205          * Overrides can also contain statics:
7206          *
7207          *      Ext.define('My.app.BarMod', {
7208          *          override: 'Ext.foo.Bar',
7209          *
7210          *          statics: {
7211          *              method: function (x) {
7212          *                  return this.callSuper([x * 2]); // call Ext.foo.Bar.method
7213          *              }
7214          *          }
7215          *      });
7216          *
7217          * IMPORTANT: An override is only included in a build if the class it overrides is
7218          * required. Otherwise, the override, like the target class, is not included.
7219          */
7220         
7221         /**
7222          * @method
7223          *
7224          * @member Ext
7225          * @alias Ext.ClassManager#create
7226          */
7227         define: function (className, data, createdFn) {
7228             if (!data.override) {
7229                 return Manager.create.apply(Manager, arguments);
7230             }
7231
7232             var requires = data.requires,
7233                 uses = data.uses,
7234                 overrideName = className;
7235
7236             className = data.override;
7237
7238             // hoist any 'requires' or 'uses' from the body onto the faux class:
7239             data = Ext.apply({}, data);
7240             delete data.requires;
7241             delete data.uses;
7242             delete data.override;
7243
7244             // make sure className is in the requires list:
7245             if (typeof requires == 'string') {
7246                 requires = [ className, requires ];
7247             } else if (requires) {
7248                 requires = requires.slice(0);
7249                 requires.unshift(className);
7250             } else {
7251                 requires = [ className ];
7252             }
7253
7254 // TODO - we need to rework this to allow the override to not require the target class
7255 //  and rather 'wait' for it in such a way that if the target class is not in the build,
7256 //  neither are any of its overrides.
7257 //
7258 //  Also, this should process the overrides for a class ASAP (ideally before any derived
7259 //  classes) if the target class 'requires' the overrides. Without some special handling, the
7260 //  overrides so required will be processed before the class and have to be bufferred even
7261 //  in a build.
7262 //
7263 // TODO - we should probably support the "config" processor on an override (to config new
7264 //  functionaliy like Aria) and maybe inheritableStatics (although static is now supported
7265 //  by callSuper). If inheritableStatics causes those statics to be included on derived class
7266 //  constructors, that probably means "no" to this since an override can come after other
7267 //  classes extend the target.
7268             return Manager.create(overrideName, {
7269                     requires: requires,
7270                     uses: uses,
7271                     isPartial: true,
7272                     constructor: function () {
7273                         throw new Error("Cannot create override '" + overrideName + "'");
7274                     }
7275                 }, function () {
7276                     var cls = Manager.get(className);
7277                     if (cls.override) { // if (normal class)
7278                         cls.override(data);
7279                     } else { // else (singleton)
7280                         cls.self.override(data);
7281                     }
7282
7283                     if (createdFn) {
7284                         // called once the override is applied and with the context of the
7285                         // overridden class (the override itself is a meaningless, name-only
7286                         // thing).
7287                         createdFn.call(cls);
7288                     }
7289                 });
7290         },
7291
7292         /**
7293          * @method
7294          * @member Ext
7295          * @alias Ext.ClassManager#getName
7296          */
7297         getClassName: alias(Manager, 'getName'),
7298
7299         /**
7300          * Returns the displayName property or className or object.
7301          * When all else fails, returns "Anonymous".
7302          * @param {Object} object
7303          * @return {String}
7304          */
7305         getDisplayName: function(object) {
7306             if (object.displayName) {
7307                 return object.displayName;
7308             }
7309
7310             if (object.$name && object.$class) {
7311                 return Ext.getClassName(object.$class) + '#' + object.$name;
7312             }
7313
7314             if (object.$className) {
7315                 return object.$className;
7316             }
7317
7318             return 'Anonymous';
7319         },
7320
7321         /**
7322          * @method
7323          * @member Ext
7324          * @alias Ext.ClassManager#getClass
7325          */
7326         getClass: alias(Manager, 'getClass'),
7327
7328         /**
7329          * Creates namespaces to be used for scoping variables and classes so that they are not global.
7330          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
7331          *
7332          *     Ext.namespace('Company', 'Company.data');
7333          *
7334          *     // equivalent and preferable to the above syntax
7335          *     Ext.namespace('Company.data');
7336          *
7337          *     Company.Widget = function() { ... };
7338          *
7339          *     Company.data.CustomStore = function(config) { ... };
7340          *
7341          * @method
7342          * @member Ext
7343          * @param {String} namespace1
7344          * @param {String} namespace2
7345          * @param {String} etc
7346          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
7347          */
7348         namespace: alias(Manager, 'createNamespaces')
7349     });
7350
7351     /**
7352      * Old name for {@link Ext#widget}.
7353      * @deprecated 4.0.0 Use {@link Ext#widget} instead.
7354      * @method
7355      * @member Ext
7356      * @alias Ext#widget
7357      */
7358     Ext.createWidget = Ext.widget;
7359
7360     /**
7361      * Convenient alias for {@link Ext#namespace Ext.namespace}
7362      * @method
7363      * @member Ext
7364      * @alias Ext#namespace
7365      */
7366     Ext.ns = Ext.namespace;
7367
7368     Class.registerPreprocessor('className', function(cls, data) {
7369         if (data.$className) {
7370             cls.$className = data.$className;
7371             cls.displayName = cls.$className;
7372         }
7373     }, true);
7374
7375     Class.setDefaultPreprocessorPosition('className', 'first');
7376
7377     Class.registerPreprocessor('xtype', function(cls, data) {
7378         var xtypes = Ext.Array.from(data.xtype),
7379             widgetPrefix = 'widget.',
7380             aliases = Ext.Array.from(data.alias),
7381             i, ln, xtype;
7382
7383         data.xtype = xtypes[0];
7384         data.xtypes = xtypes;
7385
7386         aliases = data.alias = Ext.Array.from(data.alias);
7387
7388         for (i = 0,ln = xtypes.length; i < ln; i++) {
7389             xtype = xtypes[i];
7390
7391             if (typeof xtype != 'string' || xtype.length < 1) {
7392                 throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
7393             }
7394
7395             aliases.push(widgetPrefix + xtype);
7396         }
7397
7398         data.alias = aliases;
7399     });
7400
7401     Class.setDefaultPreprocessorPosition('xtype', 'last');
7402
7403     Class.registerPreprocessor('alias', function(cls, data) {
7404         var aliases = Ext.Array.from(data.alias),
7405             xtypes = Ext.Array.from(data.xtypes),
7406             widgetPrefix = 'widget.',
7407             widgetPrefixLength = widgetPrefix.length,
7408             i, ln, alias, xtype;
7409
7410         for (i = 0, ln = aliases.length; i < ln; i++) {
7411             alias = aliases[i];
7412
7413             if (typeof alias != 'string') {
7414                 throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
7415             }
7416
7417             if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
7418                 xtype = alias.substring(widgetPrefixLength);
7419                 Ext.Array.include(xtypes, xtype);
7420
7421                 if (!cls.xtype) {
7422                     cls.xtype = data.xtype = xtype;
7423                 }
7424             }
7425         }
7426
7427         data.alias = aliases;
7428         data.xtypes = xtypes;
7429     });
7430
7431     Class.setDefaultPreprocessorPosition('alias', 'last');
7432
7433 })(Ext.Class, Ext.Function.alias);
7434
7435 /**
7436  * @class Ext.Loader
7437  * @singleton
7438  * @author Jacky Nguyen <jacky@sencha.com>
7439  * @docauthor Jacky Nguyen <jacky@sencha.com>
7440  *
7441  * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
7442  * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
7443  * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
7444  * of each approach:
7445  *
7446  * # Asynchronous Loading
7447  *
7448  * - Advantages:
7449  *       + Cross-domain
7450  *       + No web server needed: you can run the application via the file system protocol
7451  *     (i.e: `file://path/to/your/index.html`)
7452  *       + Best possible debugging experience: error messages come with the exact file name and line number
7453  *
7454  * - Disadvantages:
7455  *       + Dependencies need to be specified before-hand
7456  *
7457  * ### Method 1: Explicitly include what you need:
7458  *
7459  *     // Syntax
7460  *     Ext.require({String/Array} expressions);
7461  *
7462  *     // Example: Single alias
7463  *     Ext.require('widget.window');
7464  *
7465  *     // Example: Single class name
7466  *     Ext.require('Ext.window.Window');
7467  *
7468  *     // Example: Multiple aliases / class names mix
7469  *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
7470  *
7471  *     // Wildcards
7472  *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
7473  *
7474  * ### Method 2: Explicitly exclude what you don't need:
7475  *
7476  *     // Syntax: Note that it must be in this chaining format.
7477  *     Ext.exclude({String/Array} expressions)
7478  *        .require({String/Array} expressions);
7479  *
7480  *     // Include everything except Ext.data.*
7481  *     Ext.exclude('Ext.data.*').require('*'); 
7482  *
7483  *     // Include all widgets except widget.checkbox*,
7484  *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
7485  *     Ext.exclude('widget.checkbox*').require('widget.*');
7486  *
7487  * # Synchronous Loading on Demand
7488  *
7489  * - Advantages:
7490  *       + There's no need to specify dependencies before-hand, which is always the convenience of including
7491  *     ext-all.js before
7492  *
7493  * - Disadvantages:
7494  *       + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
7495  *       + Must be from the same domain due to XHR restriction
7496  *       + Need a web server, same reason as above
7497  *
7498  * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
7499  *
7500  *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
7501  *
7502  *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
7503  *
7504  *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
7505  *
7506  * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
7507  * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
7508  * the given class and all its dependencies.
7509  *
7510  * # Hybrid Loading - The Best of Both Worlds
7511  *
7512  * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
7513  *
7514  * ### Step 1: Start writing your application using synchronous approach.
7515  *
7516  * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
7517  *
7518  *     Ext.onReady(function(){
7519  *         var window = Ext.createWidget('window', {
7520  *             width: 500,
7521  *             height: 300,
7522  *             layout: {
7523  *                 type: 'border',
7524  *                 padding: 5
7525  *             },
7526  *             title: 'Hello Dialog',
7527  *             items: [{
7528  *                 title: 'Navigation',
7529  *                 collapsible: true,
7530  *                 region: 'west',
7531  *                 width: 200,
7532  *                 html: 'Hello',
7533  *                 split: true
7534  *             }, {
7535  *                 title: 'TabPanel',
7536  *                 region: 'center'
7537  *             }]
7538  *         });
7539  *
7540  *         window.show();
7541  *     })
7542  *
7543  * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
7544  *
7545  *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
7546  *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
7547  *
7548  * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
7549  *
7550  *     Ext.require('Ext.window.Window');
7551  *     Ext.require('Ext.layout.container.Border');
7552  *
7553  *     Ext.onReady(...);
7554  *
7555  * Everything should now load via asynchronous mode.
7556  *
7557  * # Deployment
7558  *
7559  * It's important to note that dynamic loading should only be used during development on your local machines.
7560  * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
7561  * the whole process of transitioning from / to between development / maintenance and production as easy as
7562  * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
7563  * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
7564  * array into one, then include it on top of your application.
7565  *
7566  * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
7567  */
7568 (function(Manager, Class, flexSetter, alias) {
7569
7570     var
7571         dependencyProperties = ['extend', 'mixins', 'requires'],
7572         Loader;
7573
7574     Loader = Ext.Loader = {
7575         /**
7576          * @private
7577          */
7578         documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
7579
7580         /**
7581          * Flag indicating whether there are still files being loaded
7582          * @private
7583          */
7584         isLoading: false,
7585
7586         /**
7587          * Maintain the queue for all dependencies. Each item in the array is an object of the format:
7588          * {
7589          *      requires: [...], // The required classes for this queue item
7590          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
7591          * }
7592          * @private
7593          */
7594         queue: [],
7595
7596         /**
7597          * Maintain the list of files that have already been handled so that they never get double-loaded
7598          * @private
7599          */
7600         isFileLoaded: {},
7601
7602         /**
7603          * Maintain the list of listeners to execute when all required scripts are fully loaded
7604          * @private
7605          */
7606         readyListeners: [],
7607
7608         /**
7609          * Contains optional dependencies to be loaded last
7610          * @private
7611          */
7612         optionalRequires: [],
7613
7614         /**
7615          * Map of fully qualified class names to an array of dependent classes.
7616          * @private
7617          */
7618         requiresMap: {},
7619
7620         /**
7621          * @private
7622          */
7623         numPendingFiles: 0,
7624
7625         /**
7626          * @private
7627          */
7628         numLoadedFiles: 0,
7629
7630         /** @private */
7631         hasFileLoadError: false,
7632
7633         /**
7634          * @private
7635          */
7636         classNameToFilePathMap: {},
7637
7638         /**
7639          * @property {String[]} history
7640          * An array of class names to keep track of the dependency loading order.
7641          * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
7642          */
7643         history: [],
7644
7645         /**
7646          * Configuration
7647          * @private
7648          */
7649         config: {
7650             /**
7651              * @cfg {Boolean} enabled
7652              * Whether or not to enable the dynamic dependency loading feature.
7653              */
7654             enabled: false,
7655
7656             /**
7657              * @cfg {Boolean} disableCaching
7658              * Appends current timestamp to script files to prevent caching.
7659              */
7660             disableCaching: true,
7661
7662             /**
7663              * @cfg {String} disableCachingParam
7664              * The get parameter name for the cache buster's timestamp.
7665              */
7666             disableCachingParam: '_dc',
7667
7668             /**
7669              * @cfg {Object} paths
7670              * The mapping from namespaces to file paths
7671              *
7672              *     {
7673              *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
7674              *                     // loaded from ./layout/Container.js
7675              *
7676              *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
7677              *                                     // ./src/my_own_folder/layout/Container.js
7678              *     }
7679              *
7680              * Note that all relative paths are relative to the current HTML document.
7681              * If not being specified, for example, `Other.awesome.Class`
7682              * will simply be loaded from `./Other/awesome/Class.js`
7683              */
7684             paths: {
7685                 'Ext': '.'
7686             }
7687         },
7688
7689         /**
7690          * Set the configuration for the loader. This should be called right after ext-core.js
7691          * (or ext-core-debug.js) is included in the page, e.g.:
7692          *
7693          *     <script type="text/javascript" src="ext-core-debug.js"></script>
7694          *     <script type="text/javascript">
7695          *       Ext.Loader.setConfig({
7696          *           enabled: true,
7697          *           paths: {
7698          *               'My': 'my_own_path'
7699          *           }
7700          *       });
7701          *     <script>
7702          *     <script type="text/javascript">
7703          *       Ext.require(...);
7704          *
7705          *       Ext.onReady(function() {
7706          *           // application code here
7707          *       });
7708          *     </script>
7709          *
7710          * Refer to config options of {@link Ext.Loader} for the list of possible properties.
7711          *
7712          * @param {String/Object} name  Name of the value to override, or a config object to override multiple values.
7713          * @param {Object} value  (optional) The new value to set, needed if first parameter is String.
7714          * @return {Ext.Loader} this
7715          */
7716         setConfig: function(name, value) {
7717             if (Ext.isObject(name) && arguments.length === 1) {
7718                 Ext.Object.merge(this.config, name);
7719             }
7720             else {
7721                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
7722             }
7723
7724             return this;
7725         },
7726
7727         /**
7728          * Get the config value corresponding to the specified name.
7729          * If no name is given, will return the config object.
7730          * @param {String} name The config property name
7731          * @return {Object}
7732          */
7733         getConfig: function(name) {
7734             if (name) {
7735                 return this.config[name];
7736             }
7737
7738             return this.config;
7739         },
7740
7741         /**
7742          * Sets the path of a namespace. For Example:
7743          *
7744          *     Ext.Loader.setPath('Ext', '.');
7745          *
7746          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7747          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7748          * @return {Ext.Loader} this
7749          * @method
7750          */
7751         setPath: flexSetter(function(name, path) {
7752             this.config.paths[name] = path;
7753
7754             return this;
7755         }),
7756
7757         /**
7758          * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
7759          * For example:
7760          *
7761          *     Ext.Loader.setPath('My', '/path/to/My');
7762          *
7763          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7764          *
7765          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7766          *
7767          *     Ext.Loader.setPath({
7768          *         'My': '/path/to/lib',
7769          *         'My.awesome': '/other/path/for/awesome/stuff',
7770          *         'My.awesome.more': '/more/awesome/path'
7771          *     });
7772          *
7773          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7774          *
7775          *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7776          *
7777          *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7778          *
7779          *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7780          *
7781          * @param {String} className
7782          * @return {String} path
7783          */
7784         getPath: function(className) {
7785             var path = '',
7786                 paths = this.config.paths,
7787                 prefix = this.getPrefix(className);
7788
7789             if (prefix.length > 0) {
7790                 if (prefix === className) {
7791                     return paths[prefix];
7792                 }
7793
7794                 path = paths[prefix];
7795                 className = className.substring(prefix.length + 1);
7796             }
7797
7798             if (path.length > 0) {
7799                 path += '/';
7800             }
7801
7802             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7803         },
7804
7805         /**
7806          * @private
7807          * @param {String} className
7808          */
7809         getPrefix: function(className) {
7810             var paths = this.config.paths,
7811                 prefix, deepestPrefix = '';
7812
7813             if (paths.hasOwnProperty(className)) {
7814                 return className;
7815             }
7816
7817             for (prefix in paths) {
7818                 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7819                     if (prefix.length > deepestPrefix.length) {
7820                         deepestPrefix = prefix;
7821                     }
7822                 }
7823             }
7824
7825             return deepestPrefix;
7826         },
7827
7828         /**
7829          * Refresh all items in the queue. If all dependencies for an item exist during looping,
7830          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7831          * empty
7832          * @private
7833          */
7834         refreshQueue: function() {
7835             var ln = this.queue.length,
7836                 i, item, j, requires;
7837
7838             if (ln === 0) {
7839                 this.triggerReady();
7840                 return;
7841             }
7842
7843             for (i = 0; i < ln; i++) {
7844                 item = this.queue[i];
7845
7846                 if (item) {
7847                     requires = item.requires;
7848
7849                     // Don't bother checking when the number of files loaded
7850                     // is still less than the array length
7851                     if (requires.length > this.numLoadedFiles) {
7852                         continue;
7853                     }
7854
7855                     j = 0;
7856
7857                     do {
7858                         if (Manager.isCreated(requires[j])) {
7859                             // Take out from the queue
7860                             Ext.Array.erase(requires, j, 1);
7861                         }
7862                         else {
7863                             j++;
7864                         }
7865                     } while (j < requires.length);
7866
7867                     if (item.requires.length === 0) {
7868                         Ext.Array.erase(this.queue, i, 1);
7869                         item.callback.call(item.scope);
7870                         this.refreshQueue();
7871                         break;
7872                     }
7873                 }
7874             }
7875
7876             return this;
7877         },
7878
7879         /**
7880          * Inject a script element to document's head, call onLoad and onError accordingly
7881          * @private
7882          */
7883         injectScriptElement: function(url, onLoad, onError, scope) {
7884             var script = document.createElement('script'),
7885                 me = this,
7886                 onLoadFn = function() {
7887                     me.cleanupScriptElement(script);
7888                     onLoad.call(scope);
7889                 },
7890                 onErrorFn = function() {
7891                     me.cleanupScriptElement(script);
7892                     onError.call(scope);
7893                 };
7894
7895             script.type = 'text/javascript';
7896             script.src = url;
7897             script.onload = onLoadFn;
7898             script.onerror = onErrorFn;
7899             script.onreadystatechange = function() {
7900                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7901                     onLoadFn();
7902                 }
7903             };
7904
7905             this.documentHead.appendChild(script);
7906
7907             return script;
7908         },
7909
7910         /**
7911          * @private
7912          */
7913         cleanupScriptElement: function(script) {
7914             script.onload = null;
7915             script.onreadystatechange = null;
7916             script.onerror = null;
7917
7918             return this;
7919         },
7920
7921         /**
7922          * Load a script file, supports both asynchronous and synchronous approaches
7923          *
7924          * @param {String} url
7925          * @param {Function} onLoad
7926          * @param {Object} scope
7927          * @param {Boolean} synchronous
7928          * @private
7929          */
7930         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7931             var me = this,
7932                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7933                 fileName = url.split('/').pop(),
7934                 isCrossOriginRestricted = false,
7935                 xhr, status, onScriptError;
7936
7937             scope = scope || this;
7938
7939             this.isLoading = true;
7940
7941             if (!synchronous) {
7942                 onScriptError = function() {
7943                     onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7944                 };
7945
7946                 if (!Ext.isReady && Ext.onDocumentReady) {
7947                     Ext.onDocumentReady(function() {
7948                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7949                     });
7950                 }
7951                 else {
7952                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7953                 }
7954             }
7955             else {
7956                 if (typeof XMLHttpRequest !== 'undefined') {
7957                     xhr = new XMLHttpRequest();
7958                 } else {
7959                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
7960                 }
7961
7962                 try {
7963                     xhr.open('GET', noCacheUrl, false);
7964                     xhr.send(null);
7965                 } catch (e) {
7966                     isCrossOriginRestricted = true;
7967                 }
7968
7969                 status = (xhr.status === 1223) ? 204 : xhr.status;
7970
7971                 if (!isCrossOriginRestricted) {
7972                     isCrossOriginRestricted = (status === 0);
7973                 }
7974
7975                 if (isCrossOriginRestricted
7976                 ) {
7977                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7978                                        "being loaded from a different domain or from the local file system whereby cross origin " +
7979                                        "requests are not allowed due to security reasons. Use asynchronous loading with " +
7980                                        "Ext.require instead.", synchronous);
7981                 }
7982                 else if (status >= 200 && status < 300
7983                 ) {
7984                     // Firebug friendly, file names are still shown even though they're eval'ed code
7985                     new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7986
7987                     onLoad.call(scope);
7988                 }
7989                 else {
7990                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7991                                        "verify that the file exists. " +
7992                                        "XHR status code: " + status, synchronous);
7993                 }
7994
7995                 // Prevent potential IE memory leak
7996                 xhr = null;
7997             }
7998         },
7999
8000         /**
8001          * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
8002          * Can be chained with more `require` and `exclude` methods, e.g.:
8003          *
8004          *     Ext.exclude('Ext.data.*').require('*');
8005          *
8006          *     Ext.exclude('widget.button*').require('widget.*');
8007          *
8008          * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
8009          *
8010          * @param {String/String[]} excludes
8011          * @return {Object} object contains `require` method for chaining
8012          */
8013         exclude: function(excludes) {
8014             var me = this;
8015
8016             return {
8017                 require: function(expressions, fn, scope) {
8018                     return me.require(expressions, fn, scope, excludes);
8019                 },
8020
8021                 syncRequire: function(expressions, fn, scope) {
8022                     return me.syncRequire(expressions, fn, scope, excludes);
8023                 }
8024             };
8025         },
8026
8027         /**
8028          * Synchronously loads all classes by the given names and all their direct dependencies;
8029          * optionally executes the given callback function when finishes, within the optional scope.
8030          *
8031          * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
8032          *
8033          * @param {String/String[]} expressions Can either be a string or an array of string
8034          * @param {Function} fn (Optional) The callback function
8035          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
8036          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
8037          */
8038         syncRequire: function() {
8039             this.syncModeEnabled = true;
8040             this.require.apply(this, arguments);
8041             this.refreshQueue();
8042             this.syncModeEnabled = false;
8043         },
8044
8045         /**
8046          * Loads all classes by the given names and all their direct dependencies;
8047          * optionally executes the given callback function when finishes, within the optional scope.
8048          *
8049          * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
8050          *
8051          * @param {String/String[]} expressions Can either be a string or an array of string
8052          * @param {Function} fn (Optional) The callback function
8053          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
8054          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
8055          */
8056         require: function(expressions, fn, scope, excludes) {
8057             var filePath, expression, exclude, className, excluded = {},
8058                 excludedClassNames = [],
8059                 possibleClassNames = [],
8060                 possibleClassName, classNames = [],
8061                 i, j, ln, subLn;
8062
8063             expressions = Ext.Array.from(expressions);
8064             excludes = Ext.Array.from(excludes);
8065
8066             fn = fn || Ext.emptyFn;
8067
8068             scope = scope || Ext.global;
8069
8070             for (i = 0, ln = excludes.length; i < ln; i++) {
8071                 exclude = excludes[i];
8072
8073                 if (typeof exclude === 'string' && exclude.length > 0) {
8074                     excludedClassNames = Manager.getNamesByExpression(exclude);
8075
8076                     for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
8077                         excluded[excludedClassNames[j]] = true;
8078                     }
8079                 }
8080             }
8081
8082             for (i = 0, ln = expressions.length; i < ln; i++) {
8083                 expression = expressions[i];
8084
8085                 if (typeof expression === 'string' && expression.length > 0) {
8086                     possibleClassNames = Manager.getNamesByExpression(expression);
8087
8088                     for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
8089                         possibleClassName = possibleClassNames[j];
8090
8091                         if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
8092                             Ext.Array.include(classNames, possibleClassName);
8093                         }
8094                     }
8095                 }
8096             }
8097
8098             // If the dynamic dependency feature is not being used, throw an error
8099             // if the dependencies are not defined
8100             if (!this.config.enabled) {
8101                 if (classNames.length > 0) {
8102                     Ext.Error.raise({
8103                         sourceClass: "Ext.Loader",
8104                         sourceMethod: "require",
8105                         msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
8106                              "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
8107                     });
8108                 }
8109             }
8110
8111             if (classNames.length === 0) {
8112                 fn.call(scope);
8113                 return this;
8114             }
8115
8116             this.queue.push({
8117                 requires: classNames,
8118                 callback: fn,
8119                 scope: scope
8120             });
8121
8122             classNames = classNames.slice();
8123
8124             for (i = 0, ln = classNames.length; i < ln; i++) {
8125                 className = classNames[i];
8126
8127                 if (!this.isFileLoaded.hasOwnProperty(className)) {
8128                     this.isFileLoaded[className] = false;
8129
8130                     filePath = this.getPath(className);
8131
8132                     this.classNameToFilePathMap[className] = filePath;
8133
8134                     this.numPendingFiles++;
8135
8136                     this.loadScriptFile(
8137                         filePath,
8138                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
8139                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
8140                         this,
8141                         this.syncModeEnabled
8142                     );
8143                 }
8144             }
8145
8146             return this;
8147         },
8148
8149         /**
8150          * @private
8151          * @param {String} className
8152          * @param {String} filePath
8153          */
8154         onFileLoaded: function(className, filePath) {
8155             this.numLoadedFiles++;
8156
8157             this.isFileLoaded[className] = true;
8158
8159             this.numPendingFiles--;
8160
8161             if (this.numPendingFiles === 0) {
8162                 this.refreshQueue();
8163             }
8164
8165             if (this.numPendingFiles <= 1) {
8166                 window.status = "Finished loading all dependencies, onReady fired!";
8167             }
8168             else {
8169                 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
8170             }
8171
8172             if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
8173                 var queue = this.queue,
8174                     requires,
8175                     i, ln, j, subLn, missingClasses = [], missingPaths = [];
8176
8177                 for (i = 0, ln = queue.length; i < ln; i++) {
8178                     requires = queue[i].requires;
8179
8180                     for (j = 0, subLn = requires.length; j < ln; j++) {
8181                         if (this.isFileLoaded[requires[j]]) {
8182                             missingClasses.push(requires[j]);
8183                         }
8184                     }
8185                 }
8186
8187                 if (missingClasses.length < 1) {
8188                     return;
8189                 }
8190
8191                 missingClasses = Ext.Array.filter(missingClasses, function(item) {
8192                     return !this.requiresMap.hasOwnProperty(item);
8193                 }, this);
8194
8195                 for (i = 0,ln = missingClasses.length; i < ln; i++) {
8196                     missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
8197                 }
8198
8199                 Ext.Error.raise({
8200                     sourceClass: "Ext.Loader",
8201                     sourceMethod: "onFileLoaded",
8202                     msg: "The following classes are not declared even if their files have been " +
8203                             "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
8204                             "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
8205                 });
8206             }
8207         },
8208
8209         /**
8210          * @private
8211          */
8212         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
8213             this.numPendingFiles--;
8214             this.hasFileLoadError = true;
8215
8216             Ext.Error.raise({
8217                 sourceClass: "Ext.Loader",
8218                 classToLoad: className,
8219                 loadPath: filePath,
8220                 loadingType: isSynchronous ? 'synchronous' : 'async',
8221                 msg: errorMessage
8222             });
8223         },
8224
8225         /**
8226          * @private
8227          */
8228         addOptionalRequires: function(requires) {
8229             var optionalRequires = this.optionalRequires,
8230                 i, ln, require;
8231
8232             requires = Ext.Array.from(requires);
8233
8234             for (i = 0, ln = requires.length; i < ln; i++) {
8235                 require = requires[i];
8236
8237                 Ext.Array.include(optionalRequires, require);
8238             }
8239
8240             return this;
8241         },
8242
8243         /**
8244          * @private
8245          */
8246         triggerReady: function(force) {
8247             var readyListeners = this.readyListeners,
8248                 optionalRequires, listener;
8249
8250             if (this.isLoading || force) {
8251                 this.isLoading = false;
8252
8253                 if (this.optionalRequires.length) {
8254                     // Clone then empty the array to eliminate potential recursive loop issue
8255                     optionalRequires = Ext.Array.clone(this.optionalRequires);
8256
8257                     // Empty the original array
8258                     this.optionalRequires.length = 0;
8259
8260                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
8261                     return this;
8262                 }
8263
8264                 while (readyListeners.length) {
8265                     listener = readyListeners.shift();
8266                     listener.fn.call(listener.scope);
8267
8268                     if (this.isLoading) {
8269                         return this;
8270                     }
8271                 }
8272             }
8273
8274             return this;
8275         },
8276
8277         /**
8278          * Adds new listener to be executed when all required scripts are fully loaded.
8279          *
8280          * @param {Function} fn The function callback to be executed
8281          * @param {Object} scope The execution scope (`this`) of the callback function
8282          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
8283          */
8284         onReady: function(fn, scope, withDomReady, options) {
8285             var oldFn;
8286
8287             if (withDomReady !== false && Ext.onDocumentReady) {
8288                 oldFn = fn;
8289
8290                 fn = function() {
8291                     Ext.onDocumentReady(oldFn, scope, options);
8292                 };
8293             }
8294
8295             if (!this.isLoading) {
8296                 fn.call(scope);
8297             }
8298             else {
8299                 this.readyListeners.push({
8300                     fn: fn,
8301                     scope: scope
8302                 });
8303             }
8304         },
8305
8306         /**
8307          * @private
8308          * @param {String} className
8309          */
8310         historyPush: function(className) {
8311             if (className && this.isFileLoaded.hasOwnProperty(className)) {
8312                 Ext.Array.include(this.history, className);
8313             }
8314
8315             return this;
8316         }
8317     };
8318
8319     /**
8320      * @member Ext
8321      * @method require
8322      * @alias Ext.Loader#require
8323      */
8324     Ext.require = alias(Loader, 'require');
8325
8326     /**
8327      * @member Ext
8328      * @method syncRequire
8329      * @alias Ext.Loader#syncRequire
8330      */
8331     Ext.syncRequire = alias(Loader, 'syncRequire');
8332
8333     /**
8334      * @member Ext
8335      * @method exclude
8336      * @alias Ext.Loader#exclude
8337      */
8338     Ext.exclude = alias(Loader, 'exclude');
8339
8340     /**
8341      * @member Ext
8342      * @method onReady
8343      * @alias Ext.Loader#onReady
8344      */
8345     Ext.onReady = function(fn, scope, options) {
8346         Loader.onReady(fn, scope, true, options);
8347     };
8348
8349     /**
8350      * @cfg {String[]} requires
8351      * @member Ext.Class
8352      * List of classes that have to be loaded before instantiating this class.
8353      * For example:
8354      *
8355      *     Ext.define('Mother', {
8356      *         requires: ['Child'],
8357      *         giveBirth: function() {
8358      *             // we can be sure that child class is available.
8359      *             return new Child();
8360      *         }
8361      *     });
8362      */
8363     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
8364         var me = this,
8365             dependencies = [],
8366             className = Manager.getName(cls),
8367             i, j, ln, subLn, value, propertyName, propertyValue;
8368
8369         /*
8370         Basically loop through the dependencyProperties, look for string class names and push
8371         them into a stack, regardless of whether the property's value is a string, array or object. For example:
8372         {
8373               extend: 'Ext.MyClass',
8374               requires: ['Ext.some.OtherClass'],
8375               mixins: {
8376                   observable: 'Ext.util.Observable';
8377               }
8378         }
8379         which will later be transformed into:
8380         {
8381               extend: Ext.MyClass,
8382               requires: [Ext.some.OtherClass],
8383               mixins: {
8384                   observable: Ext.util.Observable;
8385               }
8386         }
8387         */
8388
8389         for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8390             propertyName = dependencyProperties[i];
8391
8392             if (data.hasOwnProperty(propertyName)) {
8393                 propertyValue = data[propertyName];
8394
8395                 if (typeof propertyValue === 'string') {
8396                     dependencies.push(propertyValue);
8397                 }
8398                 else if (propertyValue instanceof Array) {
8399                     for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8400                         value = propertyValue[j];
8401
8402                         if (typeof value === 'string') {
8403                             dependencies.push(value);
8404                         }
8405                     }
8406                 }
8407                 else if (typeof propertyValue != 'function') {
8408                     for (j in propertyValue) {
8409                         if (propertyValue.hasOwnProperty(j)) {
8410                             value = propertyValue[j];
8411
8412                             if (typeof value === 'string') {
8413                                 dependencies.push(value);
8414                             }
8415                         }
8416                     }
8417                 }
8418             }
8419         }
8420
8421         if (dependencies.length === 0) {
8422 //            Loader.historyPush(className);
8423             return;
8424         }
8425
8426         var deadlockPath = [],
8427             requiresMap = Loader.requiresMap,
8428             detectDeadlock;
8429
8430         /*
8431         Automatically detect deadlocks before-hand,
8432         will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
8433
8434         - A extends B, then B extends A
8435         - A requires B, B requires C, then C requires A
8436
8437         The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
8438         no matter how deep the path is.
8439         */
8440
8441         if (className) {
8442             requiresMap[className] = dependencies;
8443
8444             detectDeadlock = function(cls) {
8445                 deadlockPath.push(cls);
8446
8447                 if (requiresMap[cls]) {
8448                     if (Ext.Array.contains(requiresMap[cls], className)) {
8449                         Ext.Error.raise({
8450                             sourceClass: "Ext.Loader",
8451                             msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
8452                                 deadlockPath[1] + "' " + "mutually require each other. Path: " +
8453                                 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
8454                         });
8455                     }
8456
8457                     for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
8458                         detectDeadlock(requiresMap[cls][i]);
8459                     }
8460                 }
8461             };
8462
8463             detectDeadlock(className);
8464         }
8465
8466
8467         Loader.require(dependencies, function() {
8468             for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8469                 propertyName = dependencyProperties[i];
8470
8471                 if (data.hasOwnProperty(propertyName)) {
8472                     propertyValue = data[propertyName];
8473
8474                     if (typeof propertyValue === 'string') {
8475                         data[propertyName] = Manager.get(propertyValue);
8476                     }
8477                     else if (propertyValue instanceof Array) {
8478                         for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8479                             value = propertyValue[j];
8480
8481                             if (typeof value === 'string') {
8482                                 data[propertyName][j] = Manager.get(value);
8483                             }
8484                         }
8485                     }
8486                     else if (typeof propertyValue != 'function') {
8487                         for (var k in propertyValue) {
8488                             if (propertyValue.hasOwnProperty(k)) {
8489                                 value = propertyValue[k];
8490
8491                                 if (typeof value === 'string') {
8492                                     data[propertyName][k] = Manager.get(value);
8493                                 }
8494                             }
8495                         }
8496                     }
8497                 }
8498             }
8499
8500             continueFn.call(me, cls, data);
8501         });
8502
8503         return false;
8504     }, true);
8505
8506     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
8507
8508     /**
8509      * @cfg {String[]} uses
8510      * @member Ext.Class
8511      * List of classes to load together with this class.  These aren't neccessarily loaded before
8512      * this class is instantiated. For example:
8513      *
8514      *     Ext.define('Mother', {
8515      *         uses: ['Child'],
8516      *         giveBirth: function() {
8517      *             // This code might, or might not work:
8518      *             // return new Child();
8519      *
8520      *             // Instead use Ext.create() to load the class at the spot if not loaded already:
8521      *             return Ext.create('Child');
8522      *         }
8523      *     });
8524      */
8525     Manager.registerPostprocessor('uses', function(name, cls, data) {
8526         var uses = Ext.Array.from(data.uses),
8527             items = [],
8528             i, ln, item;
8529
8530         for (i = 0, ln = uses.length; i < ln; i++) {
8531             item = uses[i];
8532
8533             if (typeof item === 'string') {
8534                 items.push(item);
8535             }
8536         }
8537
8538         Loader.addOptionalRequires(items);
8539     });
8540
8541     Manager.setDefaultPostprocessorPosition('uses', 'last');
8542
8543 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
8544
8545 /**
8546  * @author Brian Moeskau <brian@sencha.com>
8547  * @docauthor Brian Moeskau <brian@sencha.com>
8548  *
8549  * A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
8550  * errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8551  * uses the Ext 4 class system, the Error class can automatically add the source class and method from which
8552  * the error was raised. It also includes logic to automatically log the eroor to the console, if available,
8553  * with additional metadata about the error. In all cases, the error will always be thrown at the end so that
8554  * execution will halt.
8555  *
8556  * Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
8557  * handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
8558  * although in a real application it's usually a better idea to override the handling function and perform
8559  * logging or some other method of reporting the errors in a way that is meaningful to the application.
8560  *
8561  * At its simplest you can simply raise an error as a simple string from within any code:
8562  *
8563  * Example usage:
8564  *
8565  *     Ext.Error.raise('Something bad happened!');
8566  *
8567  * If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
8568  * displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
8569  * additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
8570  * In this form the `msg` attribute becomes the error description, and any other data added to the config gets
8571  * added to the error object and, if the console is available, logged to the console for inspection.
8572  *
8573  * Example usage:
8574  *
8575  *     Ext.define('Ext.Foo', {
8576  *         doSomething: function(option){
8577  *             if (someCondition === false) {
8578  *                 Ext.Error.raise({
8579  *                     msg: 'You cannot do that!',
8580  *                     option: option,   // whatever was passed into the method
8581  *                     'error code': 100 // other arbitrary info
8582  *                 });
8583  *             }
8584  *         }
8585  *     });
8586  *
8587  * If a console is available (that supports the `console.dir` function) you'll see console output like:
8588  *
8589  *     An error was raised with the following data:
8590  *     option:         Object { foo: "bar"}
8591  *         foo:        "bar"
8592  *     error code:     100
8593  *     msg:            "You cannot do that!"
8594  *     sourceClass:   "Ext.Foo"
8595  *     sourceMethod:  "doSomething"
8596  *
8597  *     uncaught exception: You cannot do that!
8598  *
8599  * As you can see, the error will report exactly where it was raised and will include as much information as the
8600  * raising code can usefully provide.
8601  *
8602  * If you want to handle all application errors globally you can simply override the static {@link #handle} method
8603  * and provide whatever handling logic you need. If the method returns true then the error is considered handled
8604  * and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
8605  *
8606  * Example usage:
8607  *
8608  *     Ext.Error.handle = function(err) {
8609  *         if (err.someProperty == 'NotReallyAnError') {
8610  *             // maybe log something to the application here if applicable
8611  *             return true;
8612  *         }
8613  *         // any non-true return value (including none) will cause the error to be thrown
8614  *     }
8615  *
8616  */
8617 Ext.Error = Ext.extend(Error, {
8618     statics: {
8619         /**
8620          * @property {Boolean} ignore
8621          * Static flag that can be used to globally disable error reporting to the browser if set to true
8622          * (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
8623          * and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
8624          * be preferable to supply a custom error {@link #handle handling} function instead.
8625          *
8626          * Example usage:
8627          *
8628          *     Ext.Error.ignore = true;
8629          *
8630          * @static
8631          */
8632         ignore: false,
8633
8634         /**
8635          * @property {Boolean} notify
8636          * Static flag that can be used to globally control error notification to the user. Unlike
8637          * Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
8638          * set to false to disable the alert notification (default is true for IE6 and IE7).
8639          *
8640          * Only the first error will generate an alert. Internally this flag is set to false when the
8641          * first error occurs prior to displaying the alert.
8642          *
8643          * This flag is not used in a release build.
8644          *
8645          * Example usage:
8646          *
8647          *     Ext.Error.notify = false;
8648          *
8649          * @static
8650          */
8651         //notify: Ext.isIE6 || Ext.isIE7,
8652
8653         /**
8654          * Raise an error that can include additional data and supports automatic console logging if available.
8655          * You can pass a string error message or an object with the `msg` attribute which will be used as the
8656          * error message. The object can contain any other name-value attributes (or objects) to be logged
8657          * along with the error.
8658          *
8659          * Note that after displaying the error message a JavaScript error will ultimately be thrown so that
8660          * execution will halt.
8661          *
8662          * Example usage:
8663          *
8664          *     Ext.Error.raise('A simple string error message');
8665          *
8666          *     // or...
8667          *
8668          *     Ext.define('Ext.Foo', {
8669          *         doSomething: function(option){
8670          *             if (someCondition === false) {
8671          *                 Ext.Error.raise({
8672          *                     msg: 'You cannot do that!',
8673          *                     option: option,   // whatever was passed into the method
8674          *                     'error code': 100 // other arbitrary info
8675          *                 });
8676          *             }
8677          *         }
8678          *     });
8679          *
8680          * @param {String/Object} err The error message string, or an object containing the attribute "msg" that will be
8681          * used as the error message. Any other data included in the object will also be logged to the browser console,
8682          * if available.
8683          * @static
8684          */
8685         raise: function(err){
8686             err = err || {};
8687             if (Ext.isString(err)) {
8688                 err = { msg: err };
8689             }
8690
8691             var method = this.raise.caller;
8692
8693             if (method) {
8694                 if (method.$name) {
8695                     err.sourceMethod = method.$name;
8696                 }
8697                 if (method.$owner) {
8698                     err.sourceClass = method.$owner.$className;
8699                 }
8700             }
8701
8702             if (Ext.Error.handle(err) !== true) {
8703                 var msg = Ext.Error.prototype.toString.call(err);
8704
8705                 Ext.log({
8706                     msg: msg,
8707                     level: 'error',
8708                     dump: err,
8709                     stack: true
8710                 });
8711
8712                 throw new Ext.Error(err);
8713             }
8714         },
8715
8716         /**
8717          * Globally handle any Ext errors that may be raised, optionally providing custom logic to
8718          * handle different errors individually. Return true from the function to bypass throwing the
8719          * error to the browser, otherwise the error will be thrown and execution will halt.
8720          *
8721          * Example usage:
8722          *
8723          *     Ext.Error.handle = function(err) {
8724          *         if (err.someProperty == 'NotReallyAnError') {
8725          *             // maybe log something to the application here if applicable
8726          *             return true;
8727          *         }
8728          *         // any non-true return value (including none) will cause the error to be thrown
8729          *     }
8730          *
8731          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes that were originally
8732          * raised with it, plus properties about the method and class from which the error originated (if raised from a
8733          * class that uses the Ext 4 class system).
8734          * @static
8735          */
8736         handle: function(){
8737             return Ext.Error.ignore;
8738         }
8739     },
8740
8741     // This is the standard property that is the name of the constructor.
8742     name: 'Ext.Error',
8743
8744     /**
8745      * Creates new Error object.
8746      * @param {String/Object} config The error message string, or an object containing the
8747      * attribute "msg" that will be used as the error message. Any other data included in
8748      * the object will be applied to the error instance and logged to the browser console, if available.
8749      */
8750     constructor: function(config){
8751         if (Ext.isString(config)) {
8752             config = { msg: config };
8753         }
8754
8755         var me = this;
8756
8757         Ext.apply(me, config);
8758
8759         me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8760         // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8761     },
8762
8763     /**
8764      * Provides a custom string representation of the error object. This is an override of the base JavaScript
8765      * `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8766      * be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8767      *
8768      * The default implementation will include the error message along with the raising class and method, if available,
8769      * but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8770      * a particular error instance, if you want to provide a custom description that will show up in the console.
8771      * @return {String} The error message. If raised from within the Ext 4 class system, the error message will also
8772      * include the raising class and method names, if available.
8773      */
8774     toString: function(){
8775         var me = this,
8776             className = me.className ? me.className  : '',
8777             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8778             msg = me.msg || '(No description provided)';
8779
8780         return className + methodName + msg;
8781     }
8782 });
8783
8784 /*
8785  * This mechanism is used to notify the user of the first error encountered on the page. This
8786  * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8787  * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8788  * where exceptions are handled in a try/catch.
8789  */
8790 (function () {
8791     var prevOnError, timer, errors = 0,
8792         extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
8793         win = Ext.global;
8794
8795     if (typeof window === 'undefined') {
8796         return; // build system or some such environment...
8797     }
8798
8799     // This method is called to notify the user of the current error status.
8800     function notify () {
8801         var counters = Ext.log.counters,
8802             supports = Ext.supports,
8803             hasOnError = supports && supports.WindowOnError; // TODO - timing
8804
8805         // Put log counters to the status bar (for most browsers):
8806         if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
8807             var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
8808                         'Info:',counters.info, 'Log:',counters.log].join(' ');
8809             if (errors) {
8810                 msg = '*** Errors: ' + errors + ' - ' + msg;
8811             } else if (counters.error) {
8812                 msg = '*** ' + msg;
8813             }
8814             win.status = msg;
8815         }
8816
8817         // Display an alert on the first error:
8818         if (!Ext.isDefined(Ext.Error.notify)) {
8819             Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
8820         }
8821         if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
8822             Ext.Error.notify = false;
8823
8824             if (timer) {
8825                 win.clearInterval(timer); // ticks can queue up so stop...
8826                 timer = null;
8827             }
8828
8829             alert('Unhandled error on page: See console or log');
8830             poll();
8831         }
8832     }
8833
8834     // Sets up polling loop. This is the only way to know about errors in some browsers
8835     // (Opera/Safari) and is the only way to update the status bar for warnings and other
8836     // non-errors.
8837     function poll () {
8838         timer = win.setInterval(notify, 1000);
8839     }
8840
8841     // window.onerror sounds ideal but it prevents the built-in error dialog from doing
8842     // its (better) thing.
8843     poll();
8844 })();
8845
8846
8847
8848 /*
8849
8850 This file is part of Ext JS 4
8851
8852 Copyright (c) 2011 Sencha Inc
8853
8854 Contact:  http://www.sencha.com/contact
8855
8856 GNU General Public License Usage
8857 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
8858
8859 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
8860
8861 */
8862 /**
8863  * @class Ext.JSON
8864  * Modified version of Douglas Crockford's JSON.js that doesn't
8865  * mess with the Object prototype
8866  * http://www.json.org/js.html
8867  * @singleton
8868  */
8869 Ext.JSON = new(function() {
8870     var useHasOwn = !! {}.hasOwnProperty,
8871     isNative = function() {
8872         var useNative = null;
8873
8874         return function() {
8875             if (useNative === null) {
8876                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
8877             }
8878
8879             return useNative;
8880         };
8881     }(),
8882     pad = function(n) {
8883         return n < 10 ? "0" + n : n;
8884     },
8885     doDecode = function(json) {
8886         return eval("(" + json + ')');
8887     },
8888     doEncode = function(o) {
8889         if (!Ext.isDefined(o) || o === null) {
8890             return "null";
8891         } else if (Ext.isArray(o)) {
8892             return encodeArray(o);
8893         } else if (Ext.isDate(o)) {
8894             return Ext.JSON.encodeDate(o);
8895         } else if (Ext.isString(o)) {
8896             return encodeString(o);
8897         } else if (typeof o == "number") {
8898             //don't use isNumber here, since finite checks happen inside isNumber
8899             return isFinite(o) ? String(o) : "null";
8900         } else if (Ext.isBoolean(o)) {
8901             return String(o);
8902         } else if (Ext.isObject(o)) {
8903             return encodeObject(o);
8904         } else if (typeof o === "function") {
8905             return "null";
8906         }
8907         return 'undefined';
8908     },
8909     m = {
8910         "\b": '\\b',
8911         "\t": '\\t',
8912         "\n": '\\n',
8913         "\f": '\\f',
8914         "\r": '\\r',
8915         '"': '\\"',
8916         "\\": '\\\\',
8917         '\x0b': '\\u000b' //ie doesn't handle \v
8918     },
8919     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
8920     encodeString = function(s) {
8921         return '"' + s.replace(charToReplace, function(a) {
8922             var c = m[a];
8923             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8924         }) + '"';
8925     },
8926     encodeArray = function(o) {
8927         var a = ["[", ""],
8928         // Note empty string in case there are no serializable members.
8929         len = o.length,
8930         i;
8931         for (i = 0; i < len; i += 1) {
8932             a.push(doEncode(o[i]), ',');
8933         }
8934         // Overwrite trailing comma (or empty string)
8935         a[a.length - 1] = ']';
8936         return a.join("");
8937     },
8938     encodeObject = function(o) {
8939         var a = ["{", ""],
8940         // Note empty string in case there are no serializable members.
8941         i;
8942         for (i in o) {
8943             if (!useHasOwn || o.hasOwnProperty(i)) {
8944                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
8945             }
8946         }
8947         // Overwrite trailing comma (or empty string)
8948         a[a.length - 1] = '}';
8949         return a.join("");
8950     };
8951
8952     /**
8953      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
8954      * <b>The returned value includes enclosing double quotation marks.</b></p>
8955      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
8956      * <p>To override this:</p><pre><code>
8957 Ext.JSON.encodeDate = function(d) {
8958     return Ext.Date.format(d, '"Y-m-d"');
8959 };
8960      </code></pre>
8961      * @param {Date} d The Date to encode
8962      * @return {String} The string literal to use in a JSON string.
8963      */
8964     this.encodeDate = function(o) {
8965         return '"' + o.getFullYear() + "-"
8966         + pad(o.getMonth() + 1) + "-"
8967         + pad(o.getDate()) + "T"
8968         + pad(o.getHours()) + ":"
8969         + pad(o.getMinutes()) + ":"
8970         + pad(o.getSeconds()) + '"';
8971     };
8972
8973     /**
8974      * Encodes an Object, Array or other value
8975      * @param {Object} o The variable to encode
8976      * @return {String} The JSON string
8977      */
8978     this.encode = function() {
8979         var ec;
8980         return function(o) {
8981             if (!ec) {
8982                 // setup encoding function on first access
8983                 ec = isNative() ? JSON.stringify : doEncode;
8984             }
8985             return ec(o);
8986         };
8987     }();
8988
8989
8990     /**
8991      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
8992      * @param {String} json The JSON string
8993      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8994      * @return {Object} The resulting object
8995      */
8996     this.decode = function() {
8997         var dc;
8998         return function(json, safe) {
8999             if (!dc) {
9000                 // setup decoding function on first access
9001                 dc = isNative() ? JSON.parse : doDecode;
9002             }
9003             try {
9004                 return dc(json);
9005             } catch (e) {
9006                 if (safe === true) {
9007                     return null;
9008                 }
9009                 Ext.Error.raise({
9010                     sourceClass: "Ext.JSON",
9011                     sourceMethod: "decode",
9012                     msg: "You're trying to decode an invalid JSON String: " + json
9013                 });
9014             }
9015         };
9016     }();
9017
9018 })();
9019 /**
9020  * Shorthand for {@link Ext.JSON#encode}
9021  * @member Ext
9022  * @method encode
9023  * @alias Ext.JSON#encode
9024  */
9025 Ext.encode = Ext.JSON.encode;
9026 /**
9027  * Shorthand for {@link Ext.JSON#decode}
9028  * @member Ext
9029  * @method decode
9030  * @alias Ext.JSON#decode
9031  */
9032 Ext.decode = Ext.JSON.decode;
9033
9034
9035 /**
9036  * @class Ext
9037
9038  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
9039  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
9040  as direct properties of the Ext namespace.
9041
9042  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
9043  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
9044
9045  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
9046  This ensures all scripts have been loaded, preventing dependency issues. For example
9047
9048      Ext.onReady(function(){
9049          new Ext.Component({
9050              renderTo: document.body,
9051              html: 'DOM ready!'
9052          });
9053      });
9054
9055 For more information about how to use the Ext classes, see
9056
9057 - <a href="http://www.sencha.com/learn/">The Learning Center</a>
9058 - <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
9059 - <a href="http://www.sencha.com/forum/">The forums</a>
9060
9061  * @singleton
9062  * @markdown
9063  */
9064 Ext.apply(Ext, {
9065     userAgent: navigator.userAgent.toLowerCase(),
9066     cache: {},
9067     idSeed: 1000,
9068     windowId: 'ext-window',
9069     documentId: 'ext-document',
9070
9071     /**
9072      * True when the document is fully initialized and ready for action
9073      * @type Boolean
9074      */
9075     isReady: false,
9076
9077     /**
9078      * True to automatically uncache orphaned Ext.Elements periodically
9079      * @type Boolean
9080      */
9081     enableGarbageCollector: true,
9082
9083     /**
9084      * True to automatically purge event listeners during garbageCollection.
9085      * @type Boolean
9086      */
9087     enableListenerCollection: true,
9088
9089     /**
9090      * Generates unique ids. If the element already has an id, it is unchanged
9091      * @param {HTMLElement/Ext.Element} el (optional) The element to generate an id for
9092      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
9093      * @return {String} The generated Id.
9094      */
9095     id: function(el, prefix) {
9096         var me = this,
9097             sandboxPrefix = '';
9098         el = Ext.getDom(el, true) || {};
9099         if (el === document) {
9100             el.id = me.documentId;
9101         }
9102         else if (el === window) {
9103             el.id = me.windowId;
9104         }
9105         if (!el.id) {
9106             if (me.isSandboxed) {
9107                 if (!me.uniqueGlobalNamespace) {
9108                     me.getUniqueGlobalNamespace();
9109                 }
9110                 sandboxPrefix = me.uniqueGlobalNamespace + '-';
9111             }
9112             el.id = sandboxPrefix + (prefix || "ext-gen") + (++Ext.idSeed);
9113         }
9114         return el.id;
9115     },
9116
9117     /**
9118      * Returns the current document body as an {@link Ext.Element}.
9119      * @return Ext.Element The document body
9120      */
9121     getBody: function() {
9122         return Ext.get(document.body || false);
9123     },
9124
9125     /**
9126      * Returns the current document head as an {@link Ext.Element}.
9127      * @return Ext.Element The document head
9128      * @method
9129      */
9130     getHead: function() {
9131         var head;
9132
9133         return function() {
9134             if (head == undefined) {
9135                 head = Ext.get(document.getElementsByTagName("head")[0]);
9136             }
9137
9138             return head;
9139         };
9140     }(),
9141
9142     /**
9143      * Returns the current HTML document object as an {@link Ext.Element}.
9144      * @return Ext.Element The document
9145      */
9146     getDoc: function() {
9147         return Ext.get(document);
9148     },
9149
9150     /**
9151      * This is shorthand reference to {@link Ext.ComponentManager#get}.
9152      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
9153      * @param {String} id The component {@link Ext.Component#id id}
9154      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
9155      * Class was found.
9156     */
9157     getCmp: function(id) {
9158         return Ext.ComponentManager.get(id);
9159     },
9160
9161     /**
9162      * Returns the current orientation of the mobile device
9163      * @return {String} Either 'portrait' or 'landscape'
9164      */
9165     getOrientation: function() {
9166         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
9167     },
9168
9169     /**
9170      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
9171      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
9172      * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of
9173      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
9174      * passed into this function in a single call as separate arguments.
9175      * @param {Ext.Element/Ext.Component/Ext.Element[]/Ext.Component[]...} arg1
9176      * An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
9177      */
9178     destroy: function() {
9179         var ln = arguments.length,
9180         i, arg;
9181
9182         for (i = 0; i < ln; i++) {
9183             arg = arguments[i];
9184             if (arg) {
9185                 if (Ext.isArray(arg)) {
9186                     this.destroy.apply(this, arg);
9187                 }
9188                 else if (Ext.isFunction(arg.destroy)) {
9189                     arg.destroy();
9190                 }
9191                 else if (arg.dom) {
9192                     arg.remove();
9193                 }
9194             }
9195         }
9196     },
9197
9198     /**
9199      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
9200      *
9201      * For example, these lines are equivalent:
9202      *
9203      *     Ext.callback(myFunc, this, [arg1, arg2]);
9204      *     Ext.isFunction(myFunc) && myFunc.apply(this, [arg1, arg2]);
9205      *
9206      * @param {Function} callback The callback to execute
9207      * @param {Object} scope (optional) The scope to execute in
9208      * @param {Array} args (optional) The arguments to pass to the function
9209      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
9210      */
9211     callback: function(callback, scope, args, delay){
9212         if(Ext.isFunction(callback)){
9213             args = args || [];
9214             scope = scope || window;
9215             if (delay) {
9216                 Ext.defer(callback, delay, scope, args);
9217             } else {
9218                 callback.apply(scope, args);
9219             }
9220         }
9221     },
9222
9223     /**
9224      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
9225      * @param {String} value The string to encode
9226      * @return {String} The encoded text
9227      */
9228     htmlEncode : function(value) {
9229         return Ext.String.htmlEncode(value);
9230     },
9231
9232     /**
9233      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
9234      * @param {String} value The string to decode
9235      * @return {String} The decoded text
9236      */
9237     htmlDecode : function(value) {
9238          return Ext.String.htmlDecode(value);
9239     },
9240
9241     /**
9242      * Appends content to the query string of a URL, handling logic for whether to place
9243      * a question mark or ampersand.
9244      * @param {String} url The URL to append to.
9245      * @param {String} s The content to append to the URL.
9246      * @return (String) The resulting URL
9247      */
9248     urlAppend : function(url, s) {
9249         if (!Ext.isEmpty(s)) {
9250             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
9251         }
9252         return url;
9253     }
9254 });
9255
9256
9257 Ext.ns = Ext.namespace;
9258
9259 // for old browsers
9260 window.undefined = window.undefined;
9261
9262 /**
9263  * @class Ext
9264  * Ext core utilities and functions.
9265  * @singleton
9266  */
9267 (function(){
9268 /*
9269 FF 3.6      - Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17
9270 FF 4.0.1    - Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
9271 FF 5.0      - Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0
9272
9273 IE6         - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)
9274 IE7         - Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1;)
9275 IE8         - Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
9276 IE9         - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
9277
9278 Chrome 11   - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24
9279
9280 Safari 5    - Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1
9281
9282 Opera 11.11 - Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11
9283 */
9284     var check = function(regex){
9285             return regex.test(Ext.userAgent);
9286         },
9287         isStrict = document.compatMode == "CSS1Compat",
9288         version = function (is, regex) {
9289             var m;
9290             return (is && (m = regex.exec(Ext.userAgent))) ? parseFloat(m[1]) : 0;
9291         },
9292         docMode = document.documentMode,
9293         isOpera = check(/opera/),
9294         isOpera10_5 = isOpera && check(/version\/10\.5/),
9295         isChrome = check(/\bchrome\b/),
9296         isWebKit = check(/webkit/),
9297         isSafari = !isChrome && check(/safari/),
9298         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
9299         isSafari3 = isSafari && check(/version\/3/),
9300         isSafari4 = isSafari && check(/version\/4/),
9301         isSafari5 = isSafari && check(/version\/5/),
9302         isIE = !isOpera && check(/msie/),
9303         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
9304         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
9305         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
9306         isIE6 = isIE && check(/msie 6/),
9307         isGecko = !isWebKit && check(/gecko/),
9308         isGecko3 = isGecko && check(/rv:1\.9/),
9309         isGecko4 = isGecko && check(/rv:2\.0/),
9310         isGecko5 = isGecko && check(/rv:5\./),
9311         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
9312         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
9313         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
9314         isWindows = check(/windows|win32/),
9315         isMac = check(/macintosh|mac os x/),
9316         isLinux = check(/linux/),
9317         scrollbarSize = null,
9318         chromeVersion = version(true, /\bchrome\/(\d+\.\d+)/),
9319         firefoxVersion = version(true, /\bfirefox\/(\d+\.\d+)/),
9320         ieVersion = version(isIE, /msie (\d+\.\d+)/),
9321         operaVersion = version(isOpera, /version\/(\d+\.\d+)/),
9322         safariVersion = version(isSafari, /version\/(\d+\.\d+)/),
9323         webKitVersion = version(isWebKit, /webkit\/(\d+\.\d+)/),
9324         isSecure = /^https/i.test(window.location.protocol);
9325
9326     // remove css image flicker
9327     try {
9328         document.execCommand("BackgroundImageCache", false, true);
9329     } catch(e) {}
9330
9331     function dumpObject (object) {
9332         var member, members = [];
9333
9334         // Cannot use Ext.encode since it can recurse endlessly (if we're lucky)
9335         // ...and the data could be prettier!
9336         Ext.Object.each(object, function (name, value) {
9337             if (typeof(value) === "function") {
9338                 return;
9339             }
9340
9341             if (!Ext.isDefined(value) || value === null ||
9342                     Ext.isDate(value) ||
9343                     Ext.isString(value) || (typeof(value) == "number") ||
9344                     Ext.isBoolean(value)) {
9345                 member = Ext.encode(value);
9346             } else if (Ext.isArray(value)) {
9347                 member = '[ ]';
9348             } else if (Ext.isObject(value)) {
9349                 member = '{ }';
9350             } else {
9351                 member = 'undefined';
9352             }
9353             members.push(Ext.encode(name) + ': ' + member);
9354         });
9355
9356         if (members.length) {
9357             return ' \nData: {\n  ' + members.join(',\n  ') + '\n}';
9358         }
9359         return '';
9360     }
9361
9362     function log (message) {
9363         var options, dump,
9364             con = Ext.global.console,
9365             level = 'log',
9366             indent = log.indent || 0,
9367             stack;
9368
9369         log.indent = indent;
9370
9371         if (!Ext.isString(message)) {
9372             options = message;
9373             message = options.msg || '';
9374             level = options.level || level;
9375             dump = options.dump;
9376             stack = options.stack;
9377
9378             if (options.indent) {
9379                 ++log.indent;
9380             } else if (options.outdent) {
9381                 log.indent = indent = Math.max(indent - 1, 0);
9382             }
9383
9384             if (dump && !(con && con.dir)) {
9385                 message += dumpObject(dump);
9386                 dump = null;
9387             }
9388         }
9389
9390         if (arguments.length > 1) {
9391             message += Array.prototype.slice.call(arguments, 1).join('');
9392         }
9393
9394         message = indent ? Ext.String.repeat('   ', indent) + message : message;
9395         // w/o console, all messages are equal, so munge the level into the message:
9396         if (level != 'log') {
9397             message = '[' + level.charAt(0).toUpperCase() + '] ' + message;
9398         }
9399
9400         // Not obvious, but 'console' comes and goes when Firebug is turned on/off, so
9401         // an early test may fail either direction if Firebug is toggled.
9402         //
9403         if (con) { // if (Firebug-like console)
9404             if (con[level]) {
9405                 con[level](message);
9406             } else {
9407                 con.log(message);
9408             }
9409
9410             if (dump) {
9411                 con.dir(dump);
9412             }
9413
9414             if (stack && con.trace) {
9415                 // Firebug's console.error() includes a trace already...
9416                 if (!con.firebug || level != 'error') {
9417                     con.trace();
9418                 }
9419             }
9420         } else {
9421             if (Ext.isOpera) {
9422                 opera.postError(message);
9423             } else {
9424                 var out = log.out,
9425                     max = log.max;
9426
9427                 if (out.length >= max) {
9428                     // this formula allows out.max to change (via debugger), where the
9429                     // more obvious "max/4" would not quite be the same
9430                     Ext.Array.erase(out, 0, out.length - 3 * Math.floor(max / 4)); // keep newest 75%
9431                 }
9432
9433                 out.push(message);
9434             }
9435         }
9436
9437         // Mostly informational, but the Ext.Error notifier uses them:
9438         ++log.count;
9439         ++log.counters[level];
9440     }
9441
9442     log.count = 0;
9443     log.counters = { error: 0, warn: 0, info: 0, log: 0 };
9444     log.out = [];
9445     log.max = 250;
9446     log.show = function () {
9447         window.open('','extlog').document.write([
9448             '<html><head><script type="text/javascript">',
9449                 'var lastCount = 0;',
9450                 'function update () {',
9451                     'var ext = window.opener.Ext,',
9452                         'extlog = ext && ext.log;',
9453                     'if (extlog && extlog.out && lastCount != extlog.count) {',
9454                         'lastCount = extlog.count;',
9455                         'var s = "<tt>" + extlog.out.join("<br>").replace(/[ ]/g, "&nbsp;") + "</tt>";',
9456                         'document.body.innerHTML = s;',
9457                     '}',
9458                     'setTimeout(update, 1000);',
9459                 '}',
9460                 'setTimeout(update, 1000);',
9461             '</script></head><body></body></html>'].join(''));
9462     };
9463
9464     Ext.setVersion('extjs', '4.0.7');
9465     Ext.apply(Ext, {
9466         /**
9467          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
9468          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
9469          * @type String
9470          */
9471         SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank',
9472
9473         /**
9474          * True if the {@link Ext.fx.Anim} Class is available
9475          * @type Boolean
9476          * @property enableFx
9477          */
9478
9479         /**
9480          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
9481          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
9482          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
9483          * @type Boolean
9484          */
9485         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
9486
9487         /**
9488          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
9489          * Currently not optimized for performance.
9490          * @type Boolean
9491          */
9492         enableNestedListenerRemoval : false,
9493
9494         /**
9495          * Indicates whether to use native browser parsing for JSON methods.
9496          * This option is ignored if the browser does not support native JSON methods.
9497          * <b>Note: Native JSON methods will not work with objects that have functions.
9498          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
9499          * @type Boolean
9500          */
9501         USE_NATIVE_JSON : false,
9502
9503         /**
9504          * Return the dom node for the passed String (id), dom node, or Ext.Element.
9505          * Optional 'strict' flag is needed for IE since it can return 'name' and
9506          * 'id' elements by using getElementById.
9507          * Here are some examples:
9508          * <pre><code>
9509 // gets dom node based on id
9510 var elDom = Ext.getDom('elId');
9511 // gets dom node based on the dom node
9512 var elDom1 = Ext.getDom(elDom);
9513
9514 // If we don&#39;t know if we are working with an
9515 // Ext.Element or a dom node use Ext.getDom
9516 function(el){
9517     var dom = Ext.getDom(el);
9518     // do something with the dom node
9519 }
9520          * </code></pre>
9521          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
9522          * when this method is called to be successful.
9523          * @param {String/HTMLElement/Ext.Element} el
9524          * @return HTMLElement
9525          */
9526         getDom : function(el, strict) {
9527             if (!el || !document) {
9528                 return null;
9529             }
9530             if (el.dom) {
9531                 return el.dom;
9532             } else {
9533                 if (typeof el == 'string') {
9534                     var e = document.getElementById(el);
9535                     // IE returns elements with the 'name' and 'id' attribute.
9536                     // we do a strict check to return the element with only the id attribute
9537                     if (e && isIE && strict) {
9538                         if (el == e.getAttribute('id')) {
9539                             return e;
9540                         } else {
9541                             return null;
9542                         }
9543                     }
9544                     return e;
9545                 } else {
9546                     return el;
9547                 }
9548             }
9549         },
9550
9551         /**
9552          * Removes a DOM node from the document.
9553          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
9554          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
9555          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
9556          * will be ignored if passed in.</p>
9557          * @param {HTMLElement} node The node to remove
9558          * @method
9559          */
9560         removeNode : isIE6 || isIE7 ? function() {
9561             var d;
9562             return function(n){
9563                 if(n && n.tagName != 'BODY'){
9564                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9565                     d = d || document.createElement('div');
9566                     d.appendChild(n);
9567                     d.innerHTML = '';
9568                     delete Ext.cache[n.id];
9569                 }
9570             };
9571         }() : function(n) {
9572             if (n && n.parentNode && n.tagName != 'BODY') {
9573                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9574                 n.parentNode.removeChild(n);
9575                 delete Ext.cache[n.id];
9576             }
9577         },
9578
9579         isStrict: isStrict,
9580
9581         isIEQuirks: isIE && !isStrict,
9582
9583         /**
9584          * True if the detected browser is Opera.
9585          * @type Boolean
9586          */
9587         isOpera : isOpera,
9588
9589         /**
9590          * True if the detected browser is Opera 10.5x.
9591          * @type Boolean
9592          */
9593         isOpera10_5 : isOpera10_5,
9594
9595         /**
9596          * True if the detected browser uses WebKit.
9597          * @type Boolean
9598          */
9599         isWebKit : isWebKit,
9600
9601         /**
9602          * True if the detected browser is Chrome.
9603          * @type Boolean
9604          */
9605         isChrome : isChrome,
9606
9607         /**
9608          * True if the detected browser is Safari.
9609          * @type Boolean
9610          */
9611         isSafari : isSafari,
9612
9613         /**
9614          * True if the detected browser is Safari 3.x.
9615          * @type Boolean
9616          */
9617         isSafari3 : isSafari3,
9618
9619         /**
9620          * True if the detected browser is Safari 4.x.
9621          * @type Boolean
9622          */
9623         isSafari4 : isSafari4,
9624
9625         /**
9626          * True if the detected browser is Safari 5.x.
9627          * @type Boolean
9628          */
9629         isSafari5 : isSafari5,
9630
9631         /**
9632          * True if the detected browser is Safari 2.x.
9633          * @type Boolean
9634          */
9635         isSafari2 : isSafari2,
9636
9637         /**
9638          * True if the detected browser is Internet Explorer.
9639          * @type Boolean
9640          */
9641         isIE : isIE,
9642
9643         /**
9644          * True if the detected browser is Internet Explorer 6.x.
9645          * @type Boolean
9646          */
9647         isIE6 : isIE6,
9648
9649         /**
9650          * True if the detected browser is Internet Explorer 7.x.
9651          * @type Boolean
9652          */
9653         isIE7 : isIE7,
9654
9655         /**
9656          * True if the detected browser is Internet Explorer 8.x.
9657          * @type Boolean
9658          */
9659         isIE8 : isIE8,
9660
9661         /**
9662          * True if the detected browser is Internet Explorer 9.x.
9663          * @type Boolean
9664          */
9665         isIE9 : isIE9,
9666
9667         /**
9668          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
9669          * @type Boolean
9670          */
9671         isGecko : isGecko,
9672
9673         /**
9674          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
9675          * @type Boolean
9676          */
9677         isGecko3 : isGecko3,
9678
9679         /**
9680          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
9681          * @type Boolean
9682          */
9683         isGecko4 : isGecko4,
9684
9685         /**
9686          * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x).
9687          * @type Boolean
9688          */
9689         isGecko5 : isGecko5,
9690
9691         /**
9692          * True if the detected browser uses FireFox 3.0
9693          * @type Boolean
9694          */
9695         isFF3_0 : isFF3_0,
9696
9697         /**
9698          * True if the detected browser uses FireFox 3.5
9699          * @type Boolean
9700          */
9701         isFF3_5 : isFF3_5,
9702
9703         /**
9704          * True if the detected browser uses FireFox 3.6
9705          * @type Boolean
9706          */
9707         isFF3_6 : isFF3_6,
9708
9709         /**
9710          * True if the detected browser uses FireFox 4
9711          * @type Boolean
9712          */
9713         isFF4 : 4 <= firefoxVersion && firefoxVersion < 5,
9714
9715         /**
9716          * True if the detected browser uses FireFox 5
9717          * @type Boolean
9718          */
9719         isFF5 : 5 <= firefoxVersion && firefoxVersion < 6,
9720
9721         /**
9722          * True if the detected platform is Linux.
9723          * @type Boolean
9724          */
9725         isLinux : isLinux,
9726
9727         /**
9728          * True if the detected platform is Windows.
9729          * @type Boolean
9730          */
9731         isWindows : isWindows,
9732
9733         /**
9734          * True if the detected platform is Mac OS.
9735          * @type Boolean
9736          */
9737         isMac : isMac,
9738
9739         /**
9740          * The current version of Chrome (0 if the browser is not Chrome).
9741          * @type Number
9742          */
9743         chromeVersion: chromeVersion,
9744
9745         /**
9746          * The current version of Firefox (0 if the browser is not Firefox).
9747          * @type Number
9748          */
9749         firefoxVersion: firefoxVersion,
9750
9751         /**
9752          * The current version of IE (0 if the browser is not IE). This does not account
9753          * for the documentMode of the current page, which is factored into {@link #isIE7},
9754          * {@link #isIE8} and {@link #isIE9}. Thus this is not always true:
9755          *
9756          *      Ext.isIE8 == (Ext.ieVersion == 8)
9757          *
9758          * @type Number
9759          * @markdown
9760          */
9761         ieVersion: ieVersion,
9762
9763         /**
9764          * The current version of Opera (0 if the browser is not Opera).
9765          * @type Number
9766          */
9767         operaVersion: operaVersion,
9768
9769         /**
9770          * The current version of Safari (0 if the browser is not Safari).
9771          * @type Number
9772          */
9773         safariVersion: safariVersion,
9774
9775         /**
9776          * The current version of WebKit (0 if the browser does not use WebKit).
9777          * @type Number
9778          */
9779         webKitVersion: webKitVersion,
9780
9781         /**
9782          * True if the page is running over SSL
9783          * @type Boolean
9784          */
9785         isSecure: isSecure,
9786
9787         /**
9788          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
9789          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
9790          * For other browsers it uses an inline data URL.
9791          * @type String
9792          */
9793         BLANK_IMAGE_URL : (isIE6 || isIE7) ? '/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
9794
9795         /**
9796          * <p>Utility method for returning a default value if the passed value is empty.</p>
9797          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
9798          * <li>null</li>
9799          * <li>undefined</li>
9800          * <li>an empty array</li>
9801          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
9802          * </ul></div>
9803          * @param {Object} value The value to test
9804          * @param {Object} defaultValue The value to return if the original value is empty
9805          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
9806          * @return {Object} value, if non-empty, else defaultValue
9807          * @deprecated 4.0.0 Use {@link Ext#valueFrom} instead
9808          */
9809         value : function(v, defaultValue, allowBlank){
9810             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
9811         },
9812
9813         /**
9814          * Escapes the passed string for use in a regular expression
9815          * @param {String} str
9816          * @return {String}
9817          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
9818          */
9819         escapeRe : function(s) {
9820             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
9821         },
9822
9823         /**
9824          * Applies event listeners to elements by selectors when the document is ready.
9825          * The event name is specified with an <tt>&#64;</tt> suffix.
9826          * <pre><code>
9827 Ext.addBehaviors({
9828     // add a listener for click on all anchors in element with id foo
9829     '#foo a&#64;click' : function(e, t){
9830         // do something
9831     },
9832
9833     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
9834     '#foo a, #bar span.some-class&#64;mouseover' : function(){
9835         // do something
9836     }
9837 });
9838          * </code></pre>
9839          * @param {Object} obj The list of behaviors to apply
9840          */
9841         addBehaviors : function(o){
9842             if(!Ext.isReady){
9843                 Ext.onReady(function(){
9844                     Ext.addBehaviors(o);
9845                 });
9846             } else {
9847                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
9848                     parts,
9849                     b,
9850                     s;
9851                 for (b in o) {
9852                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
9853                         s = parts[0];
9854                         if(!cache[s]){
9855                             cache[s] = Ext.select(s);
9856                         }
9857                         cache[s].on(parts[1], o[b]);
9858                     }
9859                 }
9860                 cache = null;
9861             }
9862         },
9863
9864         /**
9865          * Returns the size of the browser scrollbars. This can differ depending on
9866          * operating system settings, such as the theme or font size.
9867          * @param {Boolean} force (optional) true to force a recalculation of the value.
9868          * @return {Object} An object containing the width of a vertical scrollbar and the
9869          * height of a horizontal scrollbar.
9870          */
9871         getScrollbarSize: function (force) {
9872             if(!Ext.isReady){
9873                 return 0;
9874             }
9875
9876             if(force === true || scrollbarSize === null){
9877                 // BrowserBug: IE9
9878                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
9879                 // inaccurately reported. For IE9 only, we render on screen before removing.
9880                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets',
9881                     // Append our div, do our calculation and then remove it
9882                     div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
9883                     child = div.child('div', true),
9884                     w1 = child.offsetWidth;
9885
9886                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
9887
9888                 var w2 = child.offsetWidth, width = w1 - w2;
9889                 div.remove();
9890
9891                 // We assume width == height for now. TODO: is this always true?
9892                 scrollbarSize = { width: width, height: width };
9893             }
9894
9895             return scrollbarSize;
9896         },
9897
9898         /**
9899          * Utility method for getting the width of the browser's vertical scrollbar. This
9900          * can differ depending on operating system settings, such as the theme or font size.
9901          *
9902          * This method is deprected in favor of {@link #getScrollbarSize}.
9903          *
9904          * @param {Boolean} force (optional) true to force a recalculation of the value.
9905          * @return {Number} The width of a vertical scrollbar.
9906          * @deprecated
9907          */
9908         getScrollBarWidth: function(force){
9909             var size = Ext.getScrollbarSize(force);
9910             return size.width + 2; // legacy fudge factor
9911         },
9912
9913         /**
9914          * Copies a set of named properties fom the source object to the destination object.
9915          *
9916          * Example:
9917          *
9918          *     ImageComponent = Ext.extend(Ext.Component, {
9919          *         initComponent: function() {
9920          *             this.autoEl = { tag: 'img' };
9921          *             MyComponent.superclass.initComponent.apply(this, arguments);
9922          *             this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
9923          *         }
9924          *     });
9925          *
9926          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
9927          *
9928          * @param {Object} dest The destination object.
9929          * @param {Object} source The source object.
9930          * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
9931          * of property names to copy.
9932          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
9933          * @return {Object} The modified object.
9934          */
9935         copyTo : function(dest, source, names, usePrototypeKeys){
9936             if(typeof names == 'string'){
9937                 names = names.split(/[,;\s]/);
9938             }
9939             Ext.each(names, function(name){
9940                 if(usePrototypeKeys || source.hasOwnProperty(name)){
9941                     dest[name] = source[name];
9942                 }
9943             }, this);
9944             return dest;
9945         },
9946
9947         /**
9948          * Attempts to destroy and then remove a set of named properties of the passed object.
9949          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
9950          * @param {String...} args One or more names of the properties to destroy and remove from the object.
9951          */
9952         destroyMembers : function(o){
9953             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
9954                 Ext.destroy(o[a[i]]);
9955                 delete o[a[i]];
9956             }
9957         },
9958
9959         /**
9960          * Logs a message. If a console is present it will be used. On Opera, the method
9961          * "opera.postError" is called. In other cases, the message is logged to an array
9962          * "Ext.log.out". An attached debugger can watch this array and view the log. The
9963          * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250).
9964          * The `Ext.log.out` array can also be written to a popup window by entering the
9965          * following in the URL bar (a "bookmarklet"):
9966          *
9967          *    javascript:void(Ext.log.show());
9968          *
9969          * If additional parameters are passed, they are joined and appended to the message.
9970          * A technique for tracing entry and exit of a function is this:
9971          *
9972          *      function foo () {
9973          *          Ext.log({ indent: 1 }, '>> foo');
9974          *
9975          *          // log statements in here or methods called from here will be indented
9976          *          // by one step
9977          *
9978          *          Ext.log({ outdent: 1 }, '<< foo');
9979          *      }
9980          *
9981          * This method does nothing in a release build.
9982          *
9983          * @param {String/Object} message The message to log or an options object with any
9984          * of the following properties:
9985          *
9986          *  - `msg`: The message to log (required).
9987          *  - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
9988          *  - `dump`: An object to dump to the log as part of the message.
9989          *  - `stack`: True to include a stack trace in the log.
9990          *  - `indent`: Cause subsequent log statements to be indented one step.
9991          *  - `outdent`: Cause this and following statements to be one step less indented.
9992          * @markdown
9993          */
9994         log :
9995             log ||
9996             Ext.emptyFn,
9997
9998         /**
9999          * Partitions the set into two sets: a true set and a false set.
10000          * Example:
10001          * Example2:
10002          * <pre><code>
10003 // Example 1:
10004 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
10005
10006 // Example 2:
10007 Ext.partition(
10008     Ext.query("p"),
10009     function(val){
10010         return val.className == "class1"
10011     }
10012 );
10013 // true are those paragraph elements with a className of "class1",
10014 // false set are those that do not have that className.
10015          * </code></pre>
10016          * @param {Array/NodeList} arr The array to partition
10017          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
10018          * itself must be able to be evaluated for its truthfulness.
10019          * @return {Array} [array of truish values, array of falsy values]
10020          * @deprecated 4.0.0 Will be removed in the next major version
10021          */
10022         partition : function(arr, truth){
10023             var ret = [[],[]];
10024             Ext.each(arr, function(v, i, a) {
10025                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
10026             });
10027             return ret;
10028         },
10029
10030         /**
10031          * Invokes a method on each item in an Array.
10032          * <pre><code>
10033 // Example:
10034 Ext.invoke(Ext.query("p"), "getAttribute", "id");
10035 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
10036          * </code></pre>
10037          * @param {Array/NodeList} arr The Array of items to invoke the method on.
10038          * @param {String} methodName The method name to invoke.
10039          * @param {Object...} args Arguments to send into the method invocation.
10040          * @return {Array} The results of invoking the method on each item in the array.
10041          * @deprecated 4.0.0 Will be removed in the next major version
10042          */
10043         invoke : function(arr, methodName){
10044             var ret = [],
10045                 args = Array.prototype.slice.call(arguments, 2);
10046             Ext.each(arr, function(v,i) {
10047                 if (v && typeof v[methodName] == 'function') {
10048                     ret.push(v[methodName].apply(v, args));
10049                 } else {
10050                     ret.push(undefined);
10051                 }
10052             });
10053             return ret;
10054         },
10055
10056         /**
10057          * <p>Zips N sets together.</p>
10058          * <pre><code>
10059 // Example 1:
10060 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
10061 // Example 2:
10062 Ext.zip(
10063     [ "+", "-", "+"],
10064     [  12,  10,  22],
10065     [  43,  15,  96],
10066     function(a, b, c){
10067         return "$" + a + "" + b + "." + c
10068     }
10069 ); // ["$+12.43", "$-10.15", "$+22.96"]
10070          * </code></pre>
10071          * @param {Array/NodeList...} arr This argument may be repeated. Array(s) to contribute values.
10072          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
10073          * @return {Array} The zipped set.
10074          * @deprecated 4.0.0 Will be removed in the next major version
10075          */
10076         zip : function(){
10077             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
10078                 arrs = parts[0],
10079                 fn = parts[1][0],
10080                 len = Ext.max(Ext.pluck(arrs, "length")),
10081                 ret = [];
10082
10083             for (var i = 0; i < len; i++) {
10084                 ret[i] = [];
10085                 if(fn){
10086                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
10087                 }else{
10088                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
10089                         ret[i].push( arrs[j][i] );
10090                     }
10091                 }
10092             }
10093             return ret;
10094         },
10095
10096         /**
10097          * Turns an array into a sentence, joined by a specified connector - e.g.:
10098          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
10099          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
10100          * @param {String[]} items The array to create a sentence from
10101          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
10102          * @return {String} The sentence string
10103          * @deprecated 4.0.0 Will be removed in the next major version
10104          */
10105         toSentence: function(items, connector) {
10106             var length = items.length;
10107
10108             if (length <= 1) {
10109                 return items[0];
10110             } else {
10111                 var head = items.slice(0, length - 1),
10112                     tail = items[length - 1];
10113
10114                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
10115             }
10116         },
10117
10118         /**
10119          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
10120          * you may want to set this to true.
10121          * @type Boolean
10122          */
10123         useShims: isIE6
10124     });
10125 })();
10126
10127 /**
10128  * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
10129  *
10130  * See Ext.app.Application for details.
10131  *
10132  * @param {Object} config
10133  */
10134 Ext.application = function(config) {
10135     Ext.require('Ext.app.Application');
10136
10137     Ext.onReady(function() {
10138         Ext.create('Ext.app.Application', config);
10139     });
10140 };
10141
10142 /**
10143  * @class Ext.util.Format
10144
10145 This class is a centralized place for formatting functions. It includes
10146 functions to format various different types of data, such as text, dates and numeric values.
10147
10148 __Localization__
10149 This class contains several options for localization. These can be set once the library has loaded,
10150 all calls to the functions from that point will use the locale settings that were specified.
10151 Options include:
10152 - thousandSeparator
10153 - decimalSeparator
10154 - currenyPrecision
10155 - currencySign
10156 - currencyAtEnd
10157 This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}.
10158
10159 __Using with renderers__
10160 There are two helper functions that return a new function that can be used in conjunction with
10161 grid renderers:
10162
10163     columns: [{
10164         dataIndex: 'date',
10165         renderer: Ext.util.Format.dateRenderer('Y-m-d')
10166     }, {
10167         dataIndex: 'time',
10168         renderer: Ext.util.Format.numberRenderer('0.000')
10169     }]
10170
10171 Functions that only take a single argument can also be passed directly:
10172     columns: [{
10173         dataIndex: 'cost',
10174         renderer: Ext.util.Format.usMoney
10175     }, {
10176         dataIndex: 'productCode',
10177         renderer: Ext.util.Format.uppercase
10178     }]
10179
10180 __Using with XTemplates__
10181 XTemplates can also directly use Ext.util.Format functions:
10182
10183     new Ext.XTemplate([
10184         'Date: {startDate:date("Y-m-d")}',
10185         'Cost: {cost:usMoney}'
10186     ]);
10187
10188  * @markdown
10189  * @singleton
10190  */
10191 (function() {
10192     Ext.ns('Ext.util');
10193
10194     Ext.util.Format = {};
10195     var UtilFormat     = Ext.util.Format,
10196         stripTagsRE    = /<\/?[^>]+>/gi,
10197         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
10198         nl2brRe        = /\r?\n/g,
10199
10200         // A RegExp to remove from a number format string, all characters except digits and '.'
10201         formatCleanRe  = /[^\d\.]/g,
10202
10203         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
10204         // Created on first use. The local decimal separator character must be initialized for this to be created.
10205         I18NFormatCleanRe;
10206
10207     Ext.apply(UtilFormat, {
10208         /**
10209          * @property {String} thousandSeparator
10210          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
10211          * <p>This may be overridden in a locale file.</p>
10212          */
10213         thousandSeparator: ',',
10214
10215         /**
10216          * @property {String} decimalSeparator
10217          * <p>The character that the {@link #number} function uses as a decimal point.</p>
10218          * <p>This may be overridden in a locale file.</p>
10219          */
10220         decimalSeparator: '.',
10221
10222         /**
10223          * @property {Number} currencyPrecision
10224          * <p>The number of decimal places that the {@link #currency} function displays.</p>
10225          * <p>This may be overridden in a locale file.</p>
10226          */
10227         currencyPrecision: 2,
10228
10229         /**
10230          * @property {String} currencySign
10231          * <p>The currency sign that the {@link #currency} function displays.</p>
10232          * <p>This may be overridden in a locale file.</p>
10233          */
10234         currencySign: '$',
10235
10236         /**
10237          * @property {Boolean} currencyAtEnd
10238          * <p>This may be set to <code>true</code> to make the {@link #currency} function
10239          * append the currency sign to the formatted value.</p>
10240          * <p>This may be overridden in a locale file.</p>
10241          */
10242         currencyAtEnd: false,
10243
10244         /**
10245          * Checks a reference and converts it to empty string if it is undefined
10246          * @param {Object} value Reference to check
10247          * @return {Object} Empty string if converted, otherwise the original value
10248          */
10249         undef : function(value) {
10250             return value !== undefined ? value : "";
10251         },
10252
10253         /**
10254          * Checks a reference and converts it to the default value if it's empty
10255          * @param {Object} value Reference to check
10256          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
10257          * @return {String}
10258          */
10259         defaultValue : function(value, defaultValue) {
10260             return value !== undefined && value !== '' ? value : defaultValue;
10261         },
10262
10263         /**
10264          * Returns a substring from within an original string
10265          * @param {String} value The original text
10266          * @param {Number} start The start index of the substring
10267          * @param {Number} length The length of the substring
10268          * @return {String} The substring
10269          */
10270         substr : function(value, start, length) {
10271             return String(value).substr(start, length);
10272         },
10273
10274         /**
10275          * Converts a string to all lower case letters
10276          * @param {String} value The text to convert
10277          * @return {String} The converted text
10278          */
10279         lowercase : function(value) {
10280             return String(value).toLowerCase();
10281         },
10282
10283         /**
10284          * Converts a string to all upper case letters
10285          * @param {String} value The text to convert
10286          * @return {String} The converted text
10287          */
10288         uppercase : function(value) {
10289             return String(value).toUpperCase();
10290         },
10291
10292         /**
10293          * Format a number as US currency
10294          * @param {Number/String} value The numeric value to format
10295          * @return {String} The formatted currency string
10296          */
10297         usMoney : function(v) {
10298             return UtilFormat.currency(v, '$', 2);
10299         },
10300
10301         /**
10302          * Format a number as a currency
10303          * @param {Number/String} value The numeric value to format
10304          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
10305          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
10306          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
10307          * @return {String} The formatted currency string
10308          */
10309         currency: function(v, currencySign, decimals, end) {
10310             var negativeSign = '',
10311                 format = ",0",
10312                 i = 0;
10313             v = v - 0;
10314             if (v < 0) {
10315                 v = -v;
10316                 negativeSign = '-';
10317             }
10318             decimals = decimals || UtilFormat.currencyPrecision;
10319             format += format + (decimals > 0 ? '.' : '');
10320             for (; i < decimals; i++) {
10321                 format += '0';
10322             }
10323             v = UtilFormat.number(v, format);
10324             if ((end || UtilFormat.currencyAtEnd) === true) {
10325                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
10326             } else {
10327                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
10328             }
10329         },
10330
10331         /**
10332          * Formats the passed date using the specified format pattern.
10333          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
10334          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
10335          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
10336          * @return {String} The formatted date string.
10337          */
10338         date: function(v, format) {
10339             if (!v) {
10340                 return "";
10341             }
10342             if (!Ext.isDate(v)) {
10343                 v = new Date(Date.parse(v));
10344             }
10345             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
10346         },
10347
10348         /**
10349          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
10350          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
10351          * @return {Function} The date formatting function
10352          */
10353         dateRenderer : function(format) {
10354             return function(v) {
10355                 return UtilFormat.date(v, format);
10356             };
10357         },
10358
10359         /**
10360          * Strips all HTML tags
10361          * @param {Object} value The text from which to strip tags
10362          * @return {String} The stripped text
10363          */
10364         stripTags : function(v) {
10365             return !v ? v : String(v).replace(stripTagsRE, "");
10366         },
10367
10368         /**
10369          * Strips all script tags
10370          * @param {Object} value The text from which to strip script tags
10371          * @return {String} The stripped text
10372          */
10373         stripScripts : function(v) {
10374             return !v ? v : String(v).replace(stripScriptsRe, "");
10375         },
10376
10377         /**
10378          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
10379          * @param {Number/String} size The numeric value to format
10380          * @return {String} The formatted file size
10381          */
10382         fileSize : function(size) {
10383             if (size < 1024) {
10384                 return size + " bytes";
10385             } else if (size < 1048576) {
10386                 return (Math.round(((size*10) / 1024))/10) + " KB";
10387             } else {
10388                 return (Math.round(((size*10) / 1048576))/10) + " MB";
10389             }
10390         },
10391
10392         /**
10393          * It does simple math for use in a template, for example:<pre><code>
10394          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
10395          * </code></pre>
10396          * @return {Function} A function that operates on the passed value.
10397          * @method
10398          */
10399         math : function(){
10400             var fns = {};
10401
10402             return function(v, a){
10403                 if (!fns[a]) {
10404                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
10405                 }
10406                 return fns[a](v);
10407             };
10408         }(),
10409
10410         /**
10411          * Rounds the passed number to the required decimal precision.
10412          * @param {Number/String} value The numeric value to round.
10413          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
10414          * @return {Number} The rounded value.
10415          */
10416         round : function(value, precision) {
10417             var result = Number(value);
10418             if (typeof precision == 'number') {
10419                 precision = Math.pow(10, precision);
10420                 result = Math.round(value * precision) / precision;
10421             }
10422             return result;
10423         },
10424
10425         /**
10426          * <p>Formats the passed number according to the passed format string.</p>
10427          * <p>The number of digits after the decimal separator character specifies the number of
10428          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
10429          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
10430          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
10431          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
10432          * <p><b>New to Ext JS 4</b></p>
10433          * <p>Locale-specific characters are always used in the formatted output when inserting
10434          * thousand and decimal separators.</p>
10435          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
10436          * thousand separator, and "." as the decimal separator)</p>
10437          * <p>To allow specification of format strings according to local conventions for separator characters, add
10438          * the string <code>/i</code> to the end of the format string.</p>
10439          * <div style="margin-left:40px">examples (123456.789):
10440          * <div style="margin-left:10px">
10441          * 0 - (123456) show only digits, no precision<br>
10442          * 0.00 - (123456.78) show only digits, 2 precision<br>
10443          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
10444          * 0,000 - (123,456) show comma and digits, no precision<br>
10445          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
10446          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
10447          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
10448          * For example: 0.000,00/i
10449          * </div></div>
10450          * @param {Number} v The number to format.
10451          * @param {String} format The way you would like to format this text.
10452          * @return {String} The formatted number.
10453          */
10454         number: function(v, formatString) {
10455             if (!formatString) {
10456                 return v;
10457             }
10458             v = Ext.Number.from(v, NaN);
10459             if (isNaN(v)) {
10460                 return '';
10461             }
10462             var comma = UtilFormat.thousandSeparator,
10463                 dec   = UtilFormat.decimalSeparator,
10464                 i18n  = false,
10465                 neg   = v < 0,
10466                 hasComma,
10467                 psplit;
10468
10469             v = Math.abs(v);
10470
10471             // The "/i" suffix allows caller to use a locale-specific formatting string.
10472             // Clean the format string by removing all but numerals and the decimal separator.
10473             // Then split the format string into pre and post decimal segments according to *what* the
10474             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
10475             if (formatString.substr(formatString.length - 2) == '/i') {
10476                 if (!I18NFormatCleanRe) {
10477                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
10478                 }
10479                 formatString = formatString.substr(0, formatString.length - 2);
10480                 i18n   = true;
10481                 hasComma = formatString.indexOf(comma) != -1;
10482                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
10483             } else {
10484                 hasComma = formatString.indexOf(',') != -1;
10485                 psplit = formatString.replace(formatCleanRe, '').split('.');
10486             }
10487
10488             if (1 < psplit.length) {
10489                 v = v.toFixed(psplit[1].length);
10490             } else if(2 < psplit.length) {
10491                 Ext.Error.raise({
10492                     sourceClass: "Ext.util.Format",
10493                     sourceMethod: "number",
10494                     value: v,
10495                     formatString: formatString,
10496                     msg: "Invalid number format, should have no more than 1 decimal"
10497                 });
10498             } else {
10499                 v = v.toFixed(0);
10500             }
10501
10502             var fnum = v.toString();
10503
10504             psplit = fnum.split('.');
10505
10506             if (hasComma) {
10507                 var cnum = psplit[0],
10508                     parr = [],
10509                     j    = cnum.length,
10510                     m    = Math.floor(j / 3),
10511                     n    = cnum.length % 3 || 3,
10512                     i;
10513
10514                 for (i = 0; i < j; i += n) {
10515                     if (i !== 0) {
10516                         n = 3;
10517                     }
10518
10519                     parr[parr.length] = cnum.substr(i, n);
10520                     m -= 1;
10521                 }
10522                 fnum = parr.join(comma);
10523                 if (psplit[1]) {
10524                     fnum += dec + psplit[1];
10525                 }
10526             } else {
10527                 if (psplit[1]) {
10528                     fnum = psplit[0] + dec + psplit[1];
10529                 }
10530             }
10531
10532             if (neg) {
10533                 /*
10534                  * Edge case. If we have a very small negative number it will get rounded to 0,
10535                  * however the initial check at the top will still report as negative. Replace
10536                  * everything but 1-9 and check if the string is empty to determine a 0 value.
10537                  */
10538                 neg = fnum.replace(/[^1-9]/g, '') !== '';
10539             }
10540
10541             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
10542         },
10543
10544         /**
10545          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
10546          * @param {String} format Any valid number format string for {@link #number}
10547          * @return {Function} The number formatting function
10548          */
10549         numberRenderer : function(format) {
10550             return function(v) {
10551                 return UtilFormat.number(v, format);
10552             };
10553         },
10554
10555         /**
10556          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
10557          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
10558          * if the value is 0 or greater than 1.
10559          * @param {Number} value The value to compare against
10560          * @param {String} singular The singular form of the word
10561          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
10562          */
10563         plural : function(v, s, p) {
10564             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
10565         },
10566
10567         /**
10568          * Converts newline characters to the HTML tag &lt;br/>
10569          * @param {String} The string value to format.
10570          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
10571          */
10572         nl2br : function(v) {
10573             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
10574         },
10575
10576         /**
10577          * Alias for {@link Ext.String#capitalize}.
10578          * @method
10579          * @alias Ext.String#capitalize
10580          */
10581         capitalize: Ext.String.capitalize,
10582
10583         /**
10584          * Alias for {@link Ext.String#ellipsis}.
10585          * @method
10586          * @alias Ext.String#ellipsis
10587          */
10588         ellipsis: Ext.String.ellipsis,
10589
10590         /**
10591          * Alias for {@link Ext.String#format}.
10592          * @method
10593          * @alias Ext.String#format
10594          */
10595         format: Ext.String.format,
10596
10597         /**
10598          * Alias for {@link Ext.String#htmlDecode}.
10599          * @method
10600          * @alias Ext.String#htmlDecode
10601          */
10602         htmlDecode: Ext.String.htmlDecode,
10603
10604         /**
10605          * Alias for {@link Ext.String#htmlEncode}.
10606          * @method
10607          * @alias Ext.String#htmlEncode
10608          */
10609         htmlEncode: Ext.String.htmlEncode,
10610
10611         /**
10612          * Alias for {@link Ext.String#leftPad}.
10613          * @method
10614          * @alias Ext.String#leftPad
10615          */
10616         leftPad: Ext.String.leftPad,
10617
10618         /**
10619          * Alias for {@link Ext.String#trim}.
10620          * @method
10621          * @alias Ext.String#trim
10622          */
10623         trim : Ext.String.trim,
10624
10625         /**
10626          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
10627          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
10628          * @param {Number/String} v The encoded margins
10629          * @return {Object} An object with margin sizes for top, right, bottom and left
10630          */
10631         parseBox : function(box) {
10632             if (Ext.isNumber(box)) {
10633                 box = box.toString();
10634             }
10635             var parts  = box.split(' '),
10636                 ln = parts.length;
10637
10638             if (ln == 1) {
10639                 parts[1] = parts[2] = parts[3] = parts[0];
10640             }
10641             else if (ln == 2) {
10642                 parts[2] = parts[0];
10643                 parts[3] = parts[1];
10644             }
10645             else if (ln == 3) {
10646                 parts[3] = parts[1];
10647             }
10648
10649             return {
10650                 top   :parseInt(parts[0], 10) || 0,
10651                 right :parseInt(parts[1], 10) || 0,
10652                 bottom:parseInt(parts[2], 10) || 0,
10653                 left  :parseInt(parts[3], 10) || 0
10654             };
10655         },
10656
10657         /**
10658          * Escapes the passed string for use in a regular expression
10659          * @param {String} str
10660          * @return {String}
10661          */
10662         escapeRegex : function(s) {
10663             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
10664         }
10665     });
10666 })();
10667
10668 /**
10669  * @class Ext.util.TaskRunner
10670  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
10671  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
10672  * if needed, you can create separate instances of TaskRunner.  Any number of
10673  * separate tasks can be started at any time and will run independently of each
10674  * other. Example usage:
10675  * <pre><code>
10676 // Start a simple clock task that updates a div once per second
10677 var updateClock = function(){
10678     Ext.fly('clock').update(new Date().format('g:i:s A'));
10679
10680 var task = {
10681     run: updateClock,
10682     interval: 1000 //1 second
10683 }
10684 var runner = new Ext.util.TaskRunner();
10685 runner.start(task);
10686
10687 // equivalent using TaskManager
10688 Ext.TaskManager.start({
10689     run: updateClock,
10690     interval: 1000
10691 });
10692
10693  * </code></pre>
10694  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10695  * Also see {@link Ext.util.DelayedTask}. 
10696  * 
10697  * @constructor
10698  * @param {Number} [interval=10] The minimum precision in milliseconds supported by this TaskRunner instance
10699  */
10700 Ext.ns('Ext.util');
10701
10702 Ext.util.TaskRunner = function(interval) {
10703     interval = interval || 10;
10704     var tasks = [],
10705     removeQueue = [],
10706     id = 0,
10707     running = false,
10708
10709     // private
10710     stopThread = function() {
10711         running = false;
10712         clearInterval(id);
10713         id = 0;
10714     },
10715
10716     // private
10717     startThread = function() {
10718         if (!running) {
10719             running = true;
10720             id = setInterval(runTasks, interval);
10721         }
10722     },
10723
10724     // private
10725     removeTask = function(t) {
10726         removeQueue.push(t);
10727         if (t.onStop) {
10728             t.onStop.apply(t.scope || t);
10729         }
10730     },
10731
10732     // private
10733     runTasks = function() {
10734         var rqLen = removeQueue.length,
10735             now = new Date().getTime(),
10736             i;
10737
10738         if (rqLen > 0) {
10739             for (i = 0; i < rqLen; i++) {
10740                 Ext.Array.remove(tasks, removeQueue[i]);
10741             }
10742             removeQueue = [];
10743             if (tasks.length < 1) {
10744                 stopThread();
10745                 return;
10746             }
10747         }
10748         i = 0;
10749         var t,
10750             itime,
10751             rt,
10752             len = tasks.length;
10753         for (; i < len; ++i) {
10754             t = tasks[i];
10755             itime = now - t.taskRunTime;
10756             if (t.interval <= itime) {
10757                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
10758                 t.taskRunTime = now;
10759                 if (rt === false || t.taskRunCount === t.repeat) {
10760                     removeTask(t);
10761                     return;
10762                 }
10763             }
10764             if (t.duration && t.duration <= (now - t.taskStartTime)) {
10765                 removeTask(t);
10766             }
10767         }
10768     };
10769
10770     /**
10771      * Starts a new task.
10772      * @method start
10773      * @param {Object} task <p>A config object that supports the following properties:<ul>
10774      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
10775      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
10776      * current invocation count if not.</p>
10777      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
10778      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
10779      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
10780      * should be invoked.</div></li>
10781      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
10782      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
10783      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
10784      * <code>run</code> function. Defaults to the task config object.</div></li>
10785      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
10786      * the task before stopping automatically (defaults to indefinite).</div></li>
10787      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
10788      * stopping automatically (defaults to indefinite).</div></li>
10789      * </ul></p>
10790      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
10791      * that calculations based on the repeat count can be performed.</p>
10792      * @return {Object} The task
10793      */
10794     this.start = function(task) {
10795         tasks.push(task);
10796         task.taskStartTime = new Date().getTime();
10797         task.taskRunTime = 0;
10798         task.taskRunCount = 0;
10799         startThread();
10800         return task;
10801     };
10802
10803     /**
10804      * Stops an existing running task.
10805      * @method stop
10806      * @param {Object} task The task to stop
10807      * @return {Object} The task
10808      */
10809     this.stop = function(task) {
10810         removeTask(task);
10811         return task;
10812     };
10813
10814     /**
10815      * Stops all tasks that are currently running.
10816      * @method stopAll
10817      */
10818     this.stopAll = function() {
10819         stopThread();
10820         for (var i = 0, len = tasks.length; i < len; i++) {
10821             if (tasks[i].onStop) {
10822                 tasks[i].onStop();
10823             }
10824         }
10825         tasks = [];
10826         removeQueue = [];
10827     };
10828 };
10829
10830 /**
10831  * @class Ext.TaskManager
10832  * @extends Ext.util.TaskRunner
10833  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
10834  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
10835  * <pre><code>
10836 // Start a simple clock task that updates a div once per second
10837 var task = {
10838     run: function(){
10839         Ext.fly('clock').update(new Date().format('g:i:s A'));
10840     },
10841     interval: 1000 //1 second
10842 }
10843 Ext.TaskManager.start(task);
10844 </code></pre>
10845  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10846  * @singleton
10847  */
10848 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
10849 /**
10850  * @class Ext.is
10851  * 
10852  * Determines information about the current platform the application is running on.
10853  * 
10854  * @singleton
10855  */
10856 Ext.is = {
10857     init : function(navigator) {
10858         var platforms = this.platforms,
10859             ln = platforms.length,
10860             i, platform;
10861
10862         navigator = navigator || window.navigator;
10863
10864         for (i = 0; i < ln; i++) {
10865             platform = platforms[i];
10866             this[platform.identity] = platform.regex.test(navigator[platform.property]);
10867         }
10868
10869         /**
10870          * @property Desktop True if the browser is running on a desktop machine
10871          * @type {Boolean}
10872          */
10873         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
10874         /**
10875          * @property Tablet True if the browser is running on a tablet (iPad)
10876          */
10877         this.Tablet = this.iPad;
10878         /**
10879          * @property Phone True if the browser is running on a phone.
10880          * @type {Boolean}
10881          */
10882         this.Phone = !this.Desktop && !this.Tablet;
10883         /**
10884          * @property iOS True if the browser is running on iOS
10885          * @type {Boolean}
10886          */
10887         this.iOS = this.iPhone || this.iPad || this.iPod;
10888         
10889         /**
10890          * @property Standalone Detects when application has been saved to homescreen.
10891          * @type {Boolean}
10892          */
10893         this.Standalone = !!window.navigator.standalone;
10894     },
10895     
10896     /**
10897      * @property iPhone True when the browser is running on a iPhone
10898      * @type {Boolean}
10899      */
10900     platforms: [{
10901         property: 'platform',
10902         regex: /iPhone/i,
10903         identity: 'iPhone'
10904     },
10905     
10906     /**
10907      * @property iPod True when the browser is running on a iPod
10908      * @type {Boolean}
10909      */
10910     {
10911         property: 'platform',
10912         regex: /iPod/i,
10913         identity: 'iPod'
10914     },
10915     
10916     /**
10917      * @property iPad True when the browser is running on a iPad
10918      * @type {Boolean}
10919      */
10920     {
10921         property: 'userAgent',
10922         regex: /iPad/i,
10923         identity: 'iPad'
10924     },
10925     
10926     /**
10927      * @property Blackberry True when the browser is running on a Blackberry
10928      * @type {Boolean}
10929      */
10930     {
10931         property: 'userAgent',
10932         regex: /Blackberry/i,
10933         identity: 'Blackberry'
10934     },
10935     
10936     /**
10937      * @property Android True when the browser is running on an Android device
10938      * @type {Boolean}
10939      */
10940     {
10941         property: 'userAgent',
10942         regex: /Android/i,
10943         identity: 'Android'
10944     },
10945     
10946     /**
10947      * @property Mac True when the browser is running on a Mac
10948      * @type {Boolean}
10949      */
10950     {
10951         property: 'platform',
10952         regex: /Mac/i,
10953         identity: 'Mac'
10954     },
10955     
10956     /**
10957      * @property Windows True when the browser is running on Windows
10958      * @type {Boolean}
10959      */
10960     {
10961         property: 'platform',
10962         regex: /Win/i,
10963         identity: 'Windows'
10964     },
10965     
10966     /**
10967      * @property Linux True when the browser is running on Linux
10968      * @type {Boolean}
10969      */
10970     {
10971         property: 'platform',
10972         regex: /Linux/i,
10973         identity: 'Linux'
10974     }]
10975 };
10976
10977 Ext.is.init();
10978
10979 /**
10980  * @class Ext.supports
10981  *
10982  * Determines information about features are supported in the current environment
10983  * 
10984  * @singleton
10985  */
10986 Ext.supports = {
10987     init : function() {
10988         var doc = document,
10989             div = doc.createElement('div'),
10990             tests = this.tests,
10991             ln = tests.length,
10992             i, test;
10993
10994         div.innerHTML = [
10995             '<div style="height:30px;width:50px;">',
10996                 '<div style="height:20px;width:20px;"></div>',
10997             '</div>',
10998             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
10999                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
11000             '</div>',
11001             '<div style="float:left; background-color:transparent;"></div>'
11002         ].join('');
11003
11004         doc.body.appendChild(div);
11005
11006         for (i = 0; i < ln; i++) {
11007             test = tests[i];
11008             this[test.identity] = test.fn.call(this, doc, div);
11009         }
11010
11011         doc.body.removeChild(div);
11012     },
11013
11014     /**
11015      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
11016      * @type {Boolean}
11017      */
11018     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
11019
11020     /**
11021      * @property ClassList True if document environment supports the HTML5 classList API.
11022      * @type {Boolean}
11023      */
11024     ClassList: !!document.documentElement.classList,
11025
11026     /**
11027      * @property OrientationChange True if the device supports orientation change
11028      * @type {Boolean}
11029      */
11030     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
11031     
11032     /**
11033      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
11034      * @type {Boolean}
11035      */
11036     DeviceMotion: ('ondevicemotion' in window),
11037     
11038     /**
11039      * @property Touch True if the device supports touch
11040      * @type {Boolean}
11041      */
11042     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
11043     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
11044     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
11045
11046     tests: [
11047         /**
11048          * @property Transitions True if the device supports CSS3 Transitions
11049          * @type {Boolean}
11050          */
11051         {
11052             identity: 'Transitions',
11053             fn: function(doc, div) {
11054                 var prefix = [
11055                         'webkit',
11056                         'Moz',
11057                         'o',
11058                         'ms',
11059                         'khtml'
11060                     ],
11061                     TE = 'TransitionEnd',
11062                     transitionEndName = [
11063                         prefix[0] + TE,
11064                         'transitionend', //Moz bucks the prefixing convention
11065                         prefix[2] + TE,
11066                         prefix[3] + TE,
11067                         prefix[4] + TE
11068                     ],
11069                     ln = prefix.length,
11070                     i = 0,
11071                     out = false;
11072                 div = Ext.get(div);
11073                 for (; i < ln; i++) {
11074                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
11075                         Ext.supports.CSS3Prefix = prefix[i];
11076                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
11077                         out = true;
11078                         break;
11079                     }
11080                 }
11081                 return out;
11082             }
11083         },
11084         
11085         /**
11086          * @property RightMargin True if the device supports right margin.
11087          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
11088          * @type {Boolean}
11089          */
11090         {
11091             identity: 'RightMargin',
11092             fn: function(doc, div) {
11093                 var view = doc.defaultView;
11094                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
11095             }
11096         },
11097
11098         /**
11099          * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
11100          * selection when their display style is changed. Essentially, if a text input
11101          * has focus and its display style is changed, the I-beam disappears.
11102          * 
11103          * This bug is encountered due to the work around in place for the {@link #RightMargin}
11104          * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
11105          * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
11106          * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
11107          */
11108         {
11109             identity: 'DisplayChangeInputSelectionBug',
11110             fn: function() {
11111                 var webKitVersion = Ext.webKitVersion;
11112                 // WebKit but older than Safari 5 or Chrome 6:
11113                 return 0 < webKitVersion && webKitVersion < 533;
11114             }
11115         },
11116
11117         /**
11118          * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
11119          * selection when their display style is changed. Essentially, if a text area has
11120          * focus and its display style is changed, the I-beam disappears.
11121          *
11122          * This bug is encountered due to the work around in place for the {@link #RightMargin}
11123          * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
11124          * be fixed in Chrome 11.
11125          */
11126         {
11127             identity: 'DisplayChangeTextAreaSelectionBug',
11128             fn: function() {
11129                 var webKitVersion = Ext.webKitVersion;
11130
11131                 /*
11132                 Has bug w/textarea:
11133
11134                 (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
11135                             AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
11136                             Safari/534.16
11137                 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
11138                             AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
11139                             Safari/533.21.1
11140
11141                 No bug:
11142
11143                 (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
11144                             AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
11145                             Safari/534.24
11146                 */
11147                 return 0 < webKitVersion && webKitVersion < 534.24;
11148             }
11149         },
11150
11151         /**
11152          * @property TransparentColor True if the device supports transparent color
11153          * @type {Boolean}
11154          */
11155         {
11156             identity: 'TransparentColor',
11157             fn: function(doc, div, view) {
11158                 view = doc.defaultView;
11159                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
11160             }
11161         },
11162
11163         /**
11164          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
11165          * @type {Boolean}
11166          */
11167         {
11168             identity: 'ComputedStyle',
11169             fn: function(doc, div, view) {
11170                 view = doc.defaultView;
11171                 return view && view.getComputedStyle;
11172             }
11173         },
11174         
11175         /**
11176          * @property SVG True if the device supports SVG
11177          * @type {Boolean}
11178          */
11179         {
11180             identity: 'Svg',
11181             fn: function(doc) {
11182                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
11183             }
11184         },
11185     
11186         /**
11187          * @property Canvas True if the device supports Canvas
11188          * @type {Boolean}
11189          */
11190         {
11191             identity: 'Canvas',
11192             fn: function(doc) {
11193                 return !!doc.createElement('canvas').getContext;
11194             }
11195         },
11196         
11197         /**
11198          * @property VML True if the device supports VML
11199          * @type {Boolean}
11200          */
11201         {
11202             identity: 'Vml',
11203             fn: function(doc) {
11204                 var d = doc.createElement("div");
11205                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
11206                 return (d.childNodes.length == 2);
11207             }
11208         },
11209         
11210         /**
11211          * @property Float True if the device supports CSS float
11212          * @type {Boolean}
11213          */
11214         {
11215             identity: 'Float',
11216             fn: function(doc, div) {
11217                 return !!div.lastChild.style.cssFloat;
11218             }
11219         },
11220         
11221         /**
11222          * @property AudioTag True if the device supports the HTML5 audio tag
11223          * @type {Boolean}
11224          */
11225         {
11226             identity: 'AudioTag',
11227             fn: function(doc) {
11228                 return !!doc.createElement('audio').canPlayType;
11229             }
11230         },
11231         
11232         /**
11233          * @property History True if the device supports HTML5 history
11234          * @type {Boolean}
11235          */
11236         {
11237             identity: 'History',
11238             fn: function() {
11239                 return !!(window.history && history.pushState);
11240             }
11241         },
11242         
11243         /**
11244          * @property CSS3DTransform True if the device supports CSS3DTransform
11245          * @type {Boolean}
11246          */
11247         {
11248             identity: 'CSS3DTransform',
11249             fn: function() {
11250                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
11251             }
11252         },
11253
11254                 /**
11255          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
11256          * @type {Boolean}
11257          */
11258         {
11259             identity: 'CSS3LinearGradient',
11260             fn: function(doc, div) {
11261                 var property = 'background-image:',
11262                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
11263                     w3c      = 'linear-gradient(left top, black, white)',
11264                     moz      = '-moz-' + w3c,
11265                     options  = [property + webkit, property + w3c, property + moz];
11266                 
11267                 div.style.cssText = options.join(';');
11268                 
11269                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
11270             }
11271         },
11272         
11273         /**
11274          * @property CSS3BorderRadius True if the device supports CSS3 border radius
11275          * @type {Boolean}
11276          */
11277         {
11278             identity: 'CSS3BorderRadius',
11279             fn: function(doc, div) {
11280                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
11281                     pass = false,
11282                     i;
11283                 for (i = 0; i < domPrefixes.length; i++) {
11284                     if (document.body.style[domPrefixes[i]] !== undefined) {
11285                         return true;
11286                     }
11287                 }
11288                 return pass;
11289             }
11290         },
11291         
11292         /**
11293          * @property GeoLocation True if the device supports GeoLocation
11294          * @type {Boolean}
11295          */
11296         {
11297             identity: 'GeoLocation',
11298             fn: function() {
11299                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
11300             }
11301         },
11302         /**
11303          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
11304          * @type {Boolean}
11305          */
11306         {
11307             identity: 'MouseEnterLeave',
11308             fn: function(doc, div){
11309                 return ('onmouseenter' in div && 'onmouseleave' in div);
11310             }
11311         },
11312         /**
11313          * @property MouseWheel True if the browser supports the mousewheel event
11314          * @type {Boolean}
11315          */
11316         {
11317             identity: 'MouseWheel',
11318             fn: function(doc, div) {
11319                 return ('onmousewheel' in div);
11320             }
11321         },
11322         /**
11323          * @property Opacity True if the browser supports normal css opacity
11324          * @type {Boolean}
11325          */
11326         {
11327             identity: 'Opacity',
11328             fn: function(doc, div){
11329                 // Not a strict equal comparison in case opacity can be converted to a number.
11330                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
11331                     return false;
11332                 }
11333                 div.firstChild.style.cssText = 'opacity:0.73';
11334                 return div.firstChild.style.opacity == '0.73';
11335             }
11336         },
11337         /**
11338          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
11339          * @type {Boolean}
11340          */
11341         {
11342             identity: 'Placeholder',
11343             fn: function(doc) {
11344                 return 'placeholder' in doc.createElement('input');
11345             }
11346         },
11347         
11348         /**
11349          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
11350          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
11351          * @type {Boolean}
11352          */
11353         {
11354             identity: 'Direct2DBug',
11355             fn: function() {
11356                 return Ext.isString(document.body.style.msTransformOrigin);
11357             }
11358         },
11359         /**
11360          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
11361          * @type {Boolean}
11362          */
11363         {
11364             identity: 'BoundingClientRect',
11365             fn: function(doc, div) {
11366                 return Ext.isFunction(div.getBoundingClientRect);
11367             }
11368         },
11369         {
11370             identity: 'IncludePaddingInWidthCalculation',
11371             fn: function(doc, div){
11372                 var el = Ext.get(div.childNodes[1].firstChild);
11373                 return el.getWidth() == 210;
11374             }
11375         },
11376         {
11377             identity: 'IncludePaddingInHeightCalculation',
11378             fn: function(doc, div){
11379                 var el = Ext.get(div.childNodes[1].firstChild);
11380                 return el.getHeight() == 210;
11381             }
11382         },
11383         
11384         /**
11385          * @property ArraySort True if the Array sort native method isn't bugged.
11386          * @type {Boolean}
11387          */
11388         {
11389             identity: 'ArraySort',
11390             fn: function() {
11391                 var a = [1,2,3,4,5].sort(function(){ return 0; });
11392                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
11393             }
11394         },
11395         /**
11396          * @property Range True if browser support document.createRange native method.
11397          * @type {Boolean}
11398          */
11399         {
11400             identity: 'Range',
11401             fn: function() {
11402                 return !!document.createRange;
11403             }
11404         },
11405         /**
11406          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
11407          * @type {Boolean}
11408          */
11409         {
11410             identity: 'CreateContextualFragment',
11411             fn: function() {
11412                 var range = Ext.supports.Range ? document.createRange() : false;
11413                 
11414                 return range && !!range.createContextualFragment;
11415             }
11416         },
11417
11418         /**
11419          * @property WindowOnError True if browser supports window.onerror.
11420          * @type {Boolean}
11421          */
11422         {
11423             identity: 'WindowOnError',
11424             fn: function () {
11425                 // sadly, we cannot feature detect this...
11426                 return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
11427             }
11428         }
11429     ]
11430 };
11431
11432
11433
11434 /*
11435
11436 This file is part of Ext JS 4
11437
11438 Copyright (c) 2011 Sencha Inc
11439
11440 Contact:  http://www.sencha.com/contact
11441
11442 GNU General Public License Usage
11443 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11444
11445 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
11446
11447 */
11448 /**
11449  * @class Ext.DomHelper
11450  * @alternateClassName Ext.core.DomHelper
11451  *
11452  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
11453  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
11454  * from your DOM building code.</p>
11455  *
11456  * <p><b><u>DomHelper element specification object</u></b></p>
11457  * <p>A specification object is used when creating elements. Attributes of this object
11458  * are assumed to be element attributes, except for 4 special attributes:
11459  * <div class="mdetail-params"><ul>
11460  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
11461  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
11462  * same kind of element definition objects to be created and appended. These can be nested
11463  * as deep as you want.</div></li>
11464  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
11465  * This will end up being either the "class" attribute on a HTML fragment or className
11466  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
11467  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
11468  * </ul></div></p>
11469  * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
11470  * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
11471  * contains special characters that would not normally be allowed in a double-quoted attribute value,
11472  * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
11473  * malformed HTML being created. This behavior may change in a future release.</p>
11474  *
11475  * <p><b><u>Insertion methods</u></b></p>
11476  * <p>Commonly used insertion methods:
11477  * <div class="mdetail-params"><ul>
11478  * <li><tt>{@link #append}</tt> : <div class="sub-desc"></div></li>
11479  * <li><tt>{@link #insertBefore}</tt> : <div class="sub-desc"></div></li>
11480  * <li><tt>{@link #insertAfter}</tt> : <div class="sub-desc"></div></li>
11481  * <li><tt>{@link #overwrite}</tt> : <div class="sub-desc"></div></li>
11482  * <li><tt>{@link #createTemplate}</tt> : <div class="sub-desc"></div></li>
11483  * <li><tt>{@link #insertHtml}</tt> : <div class="sub-desc"></div></li>
11484  * </ul></div></p>
11485  *
11486  * <p><b><u>Example</u></b></p>
11487  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
11488  * element with id <tt>'my-div'</tt>:<br>
11489  <pre><code>
11490 var dh = Ext.DomHelper; // create shorthand alias
11491 // specification object
11492 var spec = {
11493     id: 'my-ul',
11494     tag: 'ul',
11495     cls: 'my-list',
11496     // append children after creating
11497     children: [     // may also specify 'cn' instead of 'children'
11498         {tag: 'li', id: 'item0', html: 'List Item 0'},
11499         {tag: 'li', id: 'item1', html: 'List Item 1'},
11500         {tag: 'li', id: 'item2', html: 'List Item 2'}
11501     ]
11502 };
11503 var list = dh.append(
11504     'my-div', // the context element 'my-div' can either be the id or the actual node
11505     spec      // the specification object
11506 );
11507  </code></pre></p>
11508  * <p>Element creation specification parameters in this class may also be passed as an Array of
11509  * specification objects. This can be used to insert multiple sibling nodes into an existing
11510  * container very efficiently. For example, to add more list items to the example above:<pre><code>
11511 dh.append('my-ul', [
11512     {tag: 'li', id: 'item3', html: 'List Item 3'},
11513     {tag: 'li', id: 'item4', html: 'List Item 4'}
11514 ]);
11515  * </code></pre></p>
11516  *
11517  * <p><b><u>Templating</u></b></p>
11518  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
11519  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
11520  * insert new elements. Revisiting the example above, we could utilize templating this time:
11521  * <pre><code>
11522 // create the node
11523 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
11524 // get template
11525 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
11526
11527 for(var i = 0; i < 5, i++){
11528     tpl.append(list, [i]); // use template to append to the actual node
11529 }
11530  * </code></pre></p>
11531  * <p>An example using a template:<pre><code>
11532 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
11533
11534 var tpl = new Ext.DomHelper.createTemplate(html);
11535 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
11536 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
11537  * </code></pre></p>
11538  *
11539  * <p>The same example using named parameters:<pre><code>
11540 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11541
11542 var tpl = new Ext.DomHelper.createTemplate(html);
11543 tpl.append('blog-roll', {
11544     id: 'link1',
11545     url: 'http://www.edspencer.net/',
11546     text: "Ed&#39;s Site"
11547 });
11548 tpl.append('blog-roll', {
11549     id: 'link2',
11550     url: 'http://www.dustindiaz.com/',
11551     text: "Dustin&#39;s Site"
11552 });
11553  * </code></pre></p>
11554  *
11555  * <p><b><u>Compiling Templates</u></b></p>
11556  * <p>Templates are applied using regular expressions. The performance is great, but if
11557  * you are adding a bunch of DOM elements using the same template, you can increase
11558  * performance even further by {@link Ext.Template#compile "compiling"} the template.
11559  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
11560  * broken up at the different variable points and a dynamic function is created and eval'ed.
11561  * The generated function performs string concatenation of these parts and the passed
11562  * variables instead of using regular expressions.
11563  * <pre><code>
11564 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11565
11566 var tpl = new Ext.DomHelper.createTemplate(html);
11567 tpl.compile();
11568
11569 //... use template like normal
11570  * </code></pre></p>
11571  *
11572  * <p><b><u>Performance Boost</u></b></p>
11573  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
11574  * of DOM can significantly boost performance.</p>
11575  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
11576  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
11577  * results in the creation of a text node. Usage:</p>
11578  * <pre><code>
11579 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
11580  * </code></pre>
11581  * @singleton
11582  */
11583 Ext.ns('Ext.core');
11584 Ext.core.DomHelper = Ext.DomHelper = function(){
11585     var tempTableEl = null,
11586         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
11587         tableRe = /^table|tbody|tr|td$/i,
11588         confRe = /tag|children|cn|html$/i,
11589         tableElRe = /td|tr|tbody/i,
11590         endRe = /end/i,
11591         pub,
11592         // kill repeat to save bytes
11593         afterbegin = 'afterbegin',
11594         afterend = 'afterend',
11595         beforebegin = 'beforebegin',
11596         beforeend = 'beforeend',
11597         ts = '<table>',
11598         te = '</table>',
11599         tbs = ts+'<tbody>',
11600         tbe = '</tbody>'+te,
11601         trs = tbs + '<tr>',
11602         tre = '</tr>'+tbe;
11603
11604     // private
11605     function doInsert(el, o, returnElement, pos, sibling, append){
11606         el = Ext.getDom(el);
11607         var newNode;
11608         if (pub.useDom) {
11609             newNode = createDom(o, null);
11610             if (append) {
11611                 el.appendChild(newNode);
11612             } else {
11613                 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
11614             }
11615         } else {
11616             newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
11617         }
11618         return returnElement ? Ext.get(newNode, true) : newNode;
11619     }
11620
11621     function createDom(o, parentNode){
11622         var el,
11623             doc = document,
11624             useSet,
11625             attr,
11626             val,
11627             cn;
11628
11629         if (Ext.isArray(o)) {                       // Allow Arrays of siblings to be inserted
11630             el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
11631             for (var i = 0, l = o.length; i < l; i++) {
11632                 createDom(o[i], el);
11633             }
11634         } else if (typeof o == 'string') {         // Allow a string as a child spec.
11635             el = doc.createTextNode(o);
11636         } else {
11637             el = doc.createElement( o.tag || 'div' );
11638             useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
11639             for (attr in o) {
11640                 if(!confRe.test(attr)){
11641                     val = o[attr];
11642                     if(attr == 'cls'){
11643                         el.className = val;
11644                     }else{
11645                         if(useSet){
11646                             el.setAttribute(attr, val);
11647                         }else{
11648                             el[attr] = val;
11649                         }
11650                     }
11651                 }
11652             }
11653             Ext.DomHelper.applyStyles(el, o.style);
11654
11655             if ((cn = o.children || o.cn)) {
11656                 createDom(cn, el);
11657             } else if (o.html) {
11658                 el.innerHTML = o.html;
11659             }
11660         }
11661         if(parentNode){
11662            parentNode.appendChild(el);
11663         }
11664         return el;
11665     }
11666
11667     // build as innerHTML where available
11668     function createHtml(o){
11669         var b = '',
11670             attr,
11671             val,
11672             key,
11673             cn,
11674             i;
11675
11676         if(typeof o == "string"){
11677             b = o;
11678         } else if (Ext.isArray(o)) {
11679             for (i=0; i < o.length; i++) {
11680                 if(o[i]) {
11681                     b += createHtml(o[i]);
11682                 }
11683             }
11684         } else {
11685             b += '<' + (o.tag = o.tag || 'div');
11686             for (attr in o) {
11687                 val = o[attr];
11688                 if(!confRe.test(attr)){
11689                     if (typeof val == "object") {
11690                         b += ' ' + attr + '="';
11691                         for (key in val) {
11692                             b += key + ':' + val[key] + ';';
11693                         }
11694                         b += '"';
11695                     }else{
11696                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
11697                     }
11698                 }
11699             }
11700             // Now either just close the tag or try to add children and close the tag.
11701             if (emptyTags.test(o.tag)) {
11702                 b += '/>';
11703             } else {
11704                 b += '>';
11705                 if ((cn = o.children || o.cn)) {
11706                     b += createHtml(cn);
11707                 } else if(o.html){
11708                     b += o.html;
11709                 }
11710                 b += '</' + o.tag + '>';
11711             }
11712         }
11713         return b;
11714     }
11715
11716     function ieTable(depth, s, h, e){
11717         tempTableEl.innerHTML = [s, h, e].join('');
11718         var i = -1,
11719             el = tempTableEl,
11720             ns;
11721         while(++i < depth){
11722             el = el.firstChild;
11723         }
11724 //      If the result is multiple siblings, then encapsulate them into one fragment.
11725         ns = el.nextSibling;
11726         if (ns){
11727             var df = document.createDocumentFragment();
11728             while(el){
11729                 ns = el.nextSibling;
11730                 df.appendChild(el);
11731                 el = ns;
11732             }
11733             el = df;
11734         }
11735         return el;
11736     }
11737
11738     /**
11739      * @ignore
11740      * Nasty code for IE's broken table implementation
11741      */
11742     function insertIntoTable(tag, where, el, html) {
11743         var node,
11744             before;
11745
11746         tempTableEl = tempTableEl || document.createElement('div');
11747
11748         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
11749            !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
11750             return null;
11751         }
11752         before = where == beforebegin ? el :
11753                  where == afterend ? el.nextSibling :
11754                  where == afterbegin ? el.firstChild : null;
11755
11756         if (where == beforebegin || where == afterend) {
11757             el = el.parentNode;
11758         }
11759
11760         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
11761             node = ieTable(4, trs, html, tre);
11762         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
11763                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
11764             node = ieTable(3, tbs, html, tbe);
11765         } else {
11766             node = ieTable(2, ts, html, te);
11767         }
11768         el.insertBefore(node, before);
11769         return node;
11770     }
11771
11772     /**
11773      * @ignore
11774      * Fix for IE9 createContextualFragment missing method
11775      */
11776     function createContextualFragment(html){
11777         var div = document.createElement("div"),
11778             fragment = document.createDocumentFragment(),
11779             i = 0,
11780             length, childNodes;
11781
11782         div.innerHTML = html;
11783         childNodes = div.childNodes;
11784         length = childNodes.length;
11785
11786         for (; i < length; i++) {
11787             fragment.appendChild(childNodes[i].cloneNode(true));
11788         }
11789
11790         return fragment;
11791     }
11792
11793     pub = {
11794         /**
11795          * Returns the markup for the passed Element(s) config.
11796          * @param {Object} o The DOM object spec (and children)
11797          * @return {String}
11798          */
11799         markup : function(o){
11800             return createHtml(o);
11801         },
11802
11803         /**
11804          * Applies a style specification to an element.
11805          * @param {String/HTMLElement} el The element to apply styles to
11806          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
11807          * a function which returns such a specification.
11808          */
11809         applyStyles : function(el, styles){
11810             if (styles) {
11811                 el = Ext.fly(el);
11812                 if (typeof styles == "function") {
11813                     styles = styles.call();
11814                 }
11815                 if (typeof styles == "string") {
11816                     styles = Ext.Element.parseStyles(styles);
11817                 }
11818                 if (typeof styles == "object") {
11819                     el.setStyle(styles);
11820                 }
11821             }
11822         },
11823
11824         /**
11825          * Inserts an HTML fragment into the DOM.
11826          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
11827          *
11828          * For example take the following HTML: `<div>Contents</div>`
11829          *
11830          * Using different `where` values inserts element to the following places:
11831          *
11832          * - beforeBegin: `<HERE><div>Contents</div>`
11833          * - afterBegin: `<div><HERE>Contents</div>`
11834          * - beforeEnd: `<div>Contents<HERE></div>`
11835          * - afterEnd: `<div>Contents</div><HERE>`
11836          *
11837          * @param {HTMLElement/TextNode} el The context element
11838          * @param {String} html The HTML fragment
11839          * @return {HTMLElement} The new node
11840          */
11841         insertHtml : function(where, el, html){
11842             var hash = {},
11843                 hashVal,
11844                 range,
11845                 rangeEl,
11846                 setStart,
11847                 frag,
11848                 rs;
11849
11850             where = where.toLowerCase();
11851             // add these here because they are used in both branches of the condition.
11852             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
11853             hash[afterend] = ['AfterEnd', 'nextSibling'];
11854
11855             // if IE and context element is an HTMLElement
11856             if (el.insertAdjacentHTML) {
11857                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
11858                     return rs;
11859                 }
11860
11861                 // add these two to the hash.
11862                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
11863                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
11864                 if ((hashVal = hash[where])) {
11865                     el.insertAdjacentHTML(hashVal[0], html);
11866                     return el[hashVal[1]];
11867                 }
11868             // if (not IE and context element is an HTMLElement) or TextNode
11869             } else {
11870                 // we cannot insert anything inside a textnode so...
11871                 if (Ext.isTextNode(el)) {
11872                     where = where === 'afterbegin' ? 'beforebegin' : where;
11873                     where = where === 'beforeend' ? 'afterend' : where;
11874                 }
11875                 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
11876                 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
11877                 if (hash[where]) {
11878                     if (range) {
11879                         range[setStart](el);
11880                         frag = range.createContextualFragment(html);
11881                     } else {
11882                         frag = createContextualFragment(html);
11883                     }
11884                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
11885                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
11886                 } else {
11887                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
11888                     if (el.firstChild) {
11889                         if (range) {
11890                             range[setStart](el[rangeEl]);
11891                             frag = range.createContextualFragment(html);
11892                         } else {
11893                             frag = createContextualFragment(html);
11894                         }
11895
11896                         if(where == afterbegin){
11897                             el.insertBefore(frag, el.firstChild);
11898                         }else{
11899                             el.appendChild(frag);
11900                         }
11901                     } else {
11902                         el.innerHTML = html;
11903                     }
11904                     return el[rangeEl];
11905                 }
11906             }
11907             Ext.Error.raise({
11908                 sourceClass: 'Ext.DomHelper',
11909                 sourceMethod: 'insertHtml',
11910                 htmlToInsert: html,
11911                 targetElement: el,
11912                 msg: 'Illegal insertion point reached: "' + where + '"'
11913             });
11914         },
11915
11916         /**
11917          * Creates new DOM element(s) and inserts them before el.
11918          * @param {String/HTMLElement/Ext.Element} el The context element
11919          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11920          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11921          * @return {HTMLElement/Ext.Element} The new node
11922          */
11923         insertBefore : function(el, o, returnElement){
11924             return doInsert(el, o, returnElement, beforebegin);
11925         },
11926
11927         /**
11928          * Creates new DOM element(s) and inserts them after el.
11929          * @param {String/HTMLElement/Ext.Element} el The context element
11930          * @param {Object} o The DOM object spec (and children)
11931          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11932          * @return {HTMLElement/Ext.Element} The new node
11933          */
11934         insertAfter : function(el, o, returnElement){
11935             return doInsert(el, o, returnElement, afterend, 'nextSibling');
11936         },
11937
11938         /**
11939          * Creates new DOM element(s) and inserts them as the first child of el.
11940          * @param {String/HTMLElement/Ext.Element} el The context element
11941          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11942          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11943          * @return {HTMLElement/Ext.Element} The new node
11944          */
11945         insertFirst : function(el, o, returnElement){
11946             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
11947         },
11948
11949         /**
11950          * Creates new DOM element(s) and appends them to el.
11951          * @param {String/HTMLElement/Ext.Element} el The context element
11952          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11953          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11954          * @return {HTMLElement/Ext.Element} The new node
11955          */
11956         append : function(el, o, returnElement){
11957             return doInsert(el, o, returnElement, beforeend, '', true);
11958         },
11959
11960         /**
11961          * Creates new DOM element(s) and overwrites the contents of el with them.
11962          * @param {String/HTMLElement/Ext.Element} el The context element
11963          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11964          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11965          * @return {HTMLElement/Ext.Element} The new node
11966          */
11967         overwrite : function(el, o, returnElement){
11968             el = Ext.getDom(el);
11969             el.innerHTML = createHtml(o);
11970             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
11971         },
11972
11973         createHtml : createHtml,
11974
11975         /**
11976          * Creates new DOM element(s) without inserting them to the document.
11977          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11978          * @return {HTMLElement} The new uninserted node
11979          * @method
11980          */
11981         createDom: createDom,
11982
11983         /** True to force the use of DOM instead of html fragments @type Boolean */
11984         useDom : false,
11985
11986         /**
11987          * Creates a new Ext.Template from the DOM object spec.
11988          * @param {Object} o The DOM object spec (and children)
11989          * @return {Ext.Template} The new template
11990          */
11991         createTemplate : function(o){
11992             var html = Ext.DomHelper.createHtml(o);
11993             return Ext.create('Ext.Template', html);
11994         }
11995     };
11996     return pub;
11997 }();
11998
11999 /*
12000  * This is code is also distributed under MIT license for use
12001  * with jQuery and prototype JavaScript libraries.
12002  */
12003 /**
12004  * @class Ext.DomQuery
12005 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).
12006 <p>
12007 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>
12008
12009 <p>
12010 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.
12011 </p>
12012 <h4>Element Selectors:</h4>
12013 <ul class="list">
12014     <li> <b>*</b> any element</li>
12015     <li> <b>E</b> an element with the tag E</li>
12016     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
12017     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
12018     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
12019     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
12020 </ul>
12021 <h4>Attribute Selectors:</h4>
12022 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
12023 <ul class="list">
12024     <li> <b>E[foo]</b> has an attribute "foo"</li>
12025     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
12026     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
12027     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
12028     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
12029     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
12030     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
12031 </ul>
12032 <h4>Pseudo Classes:</h4>
12033 <ul class="list">
12034     <li> <b>E:first-child</b> E is the first child of its parent</li>
12035     <li> <b>E:last-child</b> E is the last child of its parent</li>
12036     <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>
12037     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
12038     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
12039     <li> <b>E:only-child</b> E is the only child of its parent</li>
12040     <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>
12041     <li> <b>E:first</b> the first E in the resultset</li>
12042     <li> <b>E:last</b> the last E in the resultset</li>
12043     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
12044     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
12045     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
12046     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
12047     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
12048     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
12049     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
12050     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
12051     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
12052     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
12053 </ul>
12054 <h4>CSS Value Selectors:</h4>
12055 <ul class="list">
12056     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
12057     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
12058     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
12059     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
12060     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
12061     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
12062 </ul>
12063  * @singleton
12064  */
12065 Ext.ns('Ext.core');
12066
12067 Ext.core.DomQuery = Ext.DomQuery = function(){
12068     var cache = {},
12069         simpleCache = {},
12070         valueCache = {},
12071         nonSpace = /\S/,
12072         trimRe = /^\s+|\s+$/g,
12073         tplRe = /\{(\d+)\}/g,
12074         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
12075         tagTokenRe = /^(#)?([\w-\*]+)/,
12076         nthRe = /(\d*)n\+?(\d*)/,
12077         nthRe2 = /\D/,
12078         startIdRe = /^\s*\#/,
12079         // This is for IE MSXML which does not support expandos.
12080     // IE runs the same speed using setAttribute, however FF slows way down
12081     // and Safari completely fails so they need to continue to use expandos.
12082     isIE = window.ActiveXObject ? true : false,
12083     key = 30803;
12084
12085     // this eval is stop the compressor from
12086     // renaming the variable to something shorter
12087     eval("var batch = 30803;");
12088
12089     // Retrieve the child node from a particular
12090     // parent at the specified index.
12091     function child(parent, index){
12092         var i = 0,
12093             n = parent.firstChild;
12094         while(n){
12095             if(n.nodeType == 1){
12096                if(++i == index){
12097                    return n;
12098                }
12099             }
12100             n = n.nextSibling;
12101         }
12102         return null;
12103     }
12104
12105     // retrieve the next element node
12106     function next(n){
12107         while((n = n.nextSibling) && n.nodeType != 1);
12108         return n;
12109     }
12110
12111     // retrieve the previous element node
12112     function prev(n){
12113         while((n = n.previousSibling) && n.nodeType != 1);
12114         return n;
12115     }
12116
12117     // Mark each child node with a nodeIndex skipping and
12118     // removing empty text nodes.
12119     function children(parent){
12120         var n = parent.firstChild,
12121         nodeIndex = -1,
12122         nextNode;
12123         while(n){
12124             nextNode = n.nextSibling;
12125             // clean worthless empty nodes.
12126             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
12127             parent.removeChild(n);
12128             }else{
12129             // add an expando nodeIndex
12130             n.nodeIndex = ++nodeIndex;
12131             }
12132             n = nextNode;
12133         }
12134         return this;
12135     }
12136
12137
12138     // nodeSet - array of nodes
12139     // cls - CSS Class
12140     function byClassName(nodeSet, cls){
12141         if(!cls){
12142             return nodeSet;
12143         }
12144         var result = [], ri = -1;
12145         for(var i = 0, ci; ci = nodeSet[i]; i++){
12146             if((' '+ci.className+' ').indexOf(cls) != -1){
12147                 result[++ri] = ci;
12148             }
12149         }
12150         return result;
12151     };
12152
12153     function attrValue(n, attr){
12154         // if its an array, use the first node.
12155         if(!n.tagName && typeof n.length != "undefined"){
12156             n = n[0];
12157         }
12158         if(!n){
12159             return null;
12160         }
12161
12162         if(attr == "for"){
12163             return n.htmlFor;
12164         }
12165         if(attr == "class" || attr == "className"){
12166             return n.className;
12167         }
12168         return n.getAttribute(attr) || n[attr];
12169
12170     };
12171
12172
12173     // ns - nodes
12174     // mode - false, /, >, +, ~
12175     // tagName - defaults to "*"
12176     function getNodes(ns, mode, tagName){
12177         var result = [], ri = -1, cs;
12178         if(!ns){
12179             return result;
12180         }
12181         tagName = tagName || "*";
12182         // convert to array
12183         if(typeof ns.getElementsByTagName != "undefined"){
12184             ns = [ns];
12185         }
12186
12187         // no mode specified, grab all elements by tagName
12188         // at any depth
12189         if(!mode){
12190             for(var i = 0, ni; ni = ns[i]; i++){
12191                 cs = ni.getElementsByTagName(tagName);
12192                 for(var j = 0, ci; ci = cs[j]; j++){
12193                     result[++ri] = ci;
12194                 }
12195             }
12196         // Direct Child mode (/ or >)
12197         // E > F or E/F all direct children elements of E that have the tag
12198         } else if(mode == "/" || mode == ">"){
12199             var utag = tagName.toUpperCase();
12200             for(var i = 0, ni, cn; ni = ns[i]; i++){
12201                 cn = ni.childNodes;
12202                 for(var j = 0, cj; cj = cn[j]; j++){
12203                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
12204                         result[++ri] = cj;
12205                     }
12206                 }
12207             }
12208         // Immediately Preceding mode (+)
12209         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
12210         }else if(mode == "+"){
12211             var utag = tagName.toUpperCase();
12212             for(var i = 0, n; n = ns[i]; i++){
12213                 while((n = n.nextSibling) && n.nodeType != 1);
12214                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
12215                     result[++ri] = n;
12216                 }
12217             }
12218         // Sibling mode (~)
12219         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
12220         }else if(mode == "~"){
12221             var utag = tagName.toUpperCase();
12222             for(var i = 0, n; n = ns[i]; i++){
12223                 while((n = n.nextSibling)){
12224                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
12225                         result[++ri] = n;
12226                     }
12227                 }
12228             }
12229         }
12230         return result;
12231     }
12232
12233     function concat(a, b){
12234         if(b.slice){
12235             return a.concat(b);
12236         }
12237         for(var i = 0, l = b.length; i < l; i++){
12238             a[a.length] = b[i];
12239         }
12240         return a;
12241     }
12242
12243     function byTag(cs, tagName){
12244         if(cs.tagName || cs == document){
12245             cs = [cs];
12246         }
12247         if(!tagName){
12248             return cs;
12249         }
12250         var result = [], ri = -1;
12251         tagName = tagName.toLowerCase();
12252         for(var i = 0, ci; ci = cs[i]; i++){
12253             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
12254                 result[++ri] = ci;
12255             }
12256         }
12257         return result;
12258     }
12259
12260     function byId(cs, id){
12261         if(cs.tagName || cs == document){
12262             cs = [cs];
12263         }
12264         if(!id){
12265             return cs;
12266         }
12267         var result = [], ri = -1;
12268         for(var i = 0, ci; ci = cs[i]; i++){
12269             if(ci && ci.id == id){
12270                 result[++ri] = ci;
12271                 return result;
12272             }
12273         }
12274         return result;
12275     }
12276
12277     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
12278     // custom can be "{"
12279     function byAttribute(cs, attr, value, op, custom){
12280         var result = [],
12281             ri = -1,
12282             useGetStyle = custom == "{",
12283             fn = Ext.DomQuery.operators[op],
12284             a,
12285             xml,
12286             hasXml;
12287
12288         for(var i = 0, ci; ci = cs[i]; i++){
12289             // skip non-element nodes.
12290             if(ci.nodeType != 1){
12291                 continue;
12292             }
12293             // only need to do this for the first node
12294             if(!hasXml){
12295                 xml = Ext.DomQuery.isXml(ci);
12296                 hasXml = true;
12297             }
12298
12299             // we only need to change the property names if we're dealing with html nodes, not XML
12300             if(!xml){
12301                 if(useGetStyle){
12302                     a = Ext.DomQuery.getStyle(ci, attr);
12303                 } else if (attr == "class" || attr == "className"){
12304                     a = ci.className;
12305                 } else if (attr == "for"){
12306                     a = ci.htmlFor;
12307                 } else if (attr == "href"){
12308                     // getAttribute href bug
12309                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
12310                     a = ci.getAttribute("href", 2);
12311                 } else{
12312                     a = ci.getAttribute(attr);
12313                 }
12314             }else{
12315                 a = ci.getAttribute(attr);
12316             }
12317             if((fn && fn(a, value)) || (!fn && a)){
12318                 result[++ri] = ci;
12319             }
12320         }
12321         return result;
12322     }
12323
12324     function byPseudo(cs, name, value){
12325         return Ext.DomQuery.pseudos[name](cs, value);
12326     }
12327
12328     function nodupIEXml(cs){
12329         var d = ++key,
12330             r;
12331         cs[0].setAttribute("_nodup", d);
12332         r = [cs[0]];
12333         for(var i = 1, len = cs.length; i < len; i++){
12334             var c = cs[i];
12335             if(!c.getAttribute("_nodup") != d){
12336                 c.setAttribute("_nodup", d);
12337                 r[r.length] = c;
12338             }
12339         }
12340         for(var i = 0, len = cs.length; i < len; i++){
12341             cs[i].removeAttribute("_nodup");
12342         }
12343         return r;
12344     }
12345
12346     function nodup(cs){
12347         if(!cs){
12348             return [];
12349         }
12350         var len = cs.length, c, i, r = cs, cj, ri = -1;
12351         if(!len || typeof cs.nodeType != "undefined" || len == 1){
12352             return cs;
12353         }
12354         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
12355             return nodupIEXml(cs);
12356         }
12357         var d = ++key;
12358         cs[0]._nodup = d;
12359         for(i = 1; c = cs[i]; i++){
12360             if(c._nodup != d){
12361                 c._nodup = d;
12362             }else{
12363                 r = [];
12364                 for(var j = 0; j < i; j++){
12365                     r[++ri] = cs[j];
12366                 }
12367                 for(j = i+1; cj = cs[j]; j++){
12368                     if(cj._nodup != d){
12369                         cj._nodup = d;
12370                         r[++ri] = cj;
12371                     }
12372                 }
12373                 return r;
12374             }
12375         }
12376         return r;
12377     }
12378
12379     function quickDiffIEXml(c1, c2){
12380         var d = ++key,
12381             r = [];
12382         for(var i = 0, len = c1.length; i < len; i++){
12383             c1[i].setAttribute("_qdiff", d);
12384         }
12385         for(var i = 0, len = c2.length; i < len; i++){
12386             if(c2[i].getAttribute("_qdiff") != d){
12387                 r[r.length] = c2[i];
12388             }
12389         }
12390         for(var i = 0, len = c1.length; i < len; i++){
12391            c1[i].removeAttribute("_qdiff");
12392         }
12393         return r;
12394     }
12395
12396     function quickDiff(c1, c2){
12397         var len1 = c1.length,
12398             d = ++key,
12399             r = [];
12400         if(!len1){
12401             return c2;
12402         }
12403         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
12404             return quickDiffIEXml(c1, c2);
12405         }
12406         for(var i = 0; i < len1; i++){
12407             c1[i]._qdiff = d;
12408         }
12409         for(var i = 0, len = c2.length; i < len; i++){
12410             if(c2[i]._qdiff != d){
12411                 r[r.length] = c2[i];
12412             }
12413         }
12414         return r;
12415     }
12416
12417     function quickId(ns, mode, root, id){
12418         if(ns == root){
12419            var d = root.ownerDocument || root;
12420            return d.getElementById(id);
12421         }
12422         ns = getNodes(ns, mode, "*");
12423         return byId(ns, id);
12424     }
12425
12426     return {
12427         getStyle : function(el, name){
12428             return Ext.fly(el).getStyle(name);
12429         },
12430         /**
12431          * Compiles a selector/xpath query into a reusable function. The returned function
12432          * takes one parameter "root" (optional), which is the context node from where the query should start.
12433          * @param {String} selector The selector/xpath query
12434          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
12435          * @return {Function}
12436          */
12437         compile : function(path, type){
12438             type = type || "select";
12439
12440             // setup fn preamble
12441             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
12442                 mode,
12443                 lastPath,
12444                 matchers = Ext.DomQuery.matchers,
12445                 matchersLn = matchers.length,
12446                 modeMatch,
12447                 // accept leading mode switch
12448                 lmode = path.match(modeRe);
12449
12450             if(lmode && lmode[1]){
12451                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
12452                 path = path.replace(lmode[1], "");
12453             }
12454
12455             // strip leading slashes
12456             while(path.substr(0, 1)=="/"){
12457                 path = path.substr(1);
12458             }
12459
12460             while(path && lastPath != path){
12461                 lastPath = path;
12462                 var tokenMatch = path.match(tagTokenRe);
12463                 if(type == "select"){
12464                     if(tokenMatch){
12465                         // ID Selector
12466                         if(tokenMatch[1] == "#"){
12467                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
12468                         }else{
12469                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
12470                         }
12471                         path = path.replace(tokenMatch[0], "");
12472                     }else if(path.substr(0, 1) != '@'){
12473                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
12474                     }
12475                 // type of "simple"
12476                 }else{
12477                     if(tokenMatch){
12478                         if(tokenMatch[1] == "#"){
12479                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
12480                         }else{
12481                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
12482                         }
12483                         path = path.replace(tokenMatch[0], "");
12484                     }
12485                 }
12486                 while(!(modeMatch = path.match(modeRe))){
12487                     var matched = false;
12488                     for(var j = 0; j < matchersLn; j++){
12489                         var t = matchers[j];
12490                         var m = path.match(t.re);
12491                         if(m){
12492                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
12493                                 return m[i];
12494                             });
12495                             path = path.replace(m[0], "");
12496                             matched = true;
12497                             break;
12498                         }
12499                     }
12500                     // prevent infinite loop on bad selector
12501                     if(!matched){
12502                         Ext.Error.raise({
12503                             sourceClass: 'Ext.DomQuery',
12504                             sourceMethod: 'compile',
12505                             msg: 'Error parsing selector. Parsing failed at "' + path + '"'
12506                         });
12507                     }
12508                 }
12509                 if(modeMatch[1]){
12510                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
12511                     path = path.replace(modeMatch[1], "");
12512                 }
12513             }
12514             // close fn out
12515             fn[fn.length] = "return nodup(n);\n}";
12516
12517             // eval fn and return it
12518             eval(fn.join(""));
12519             return f;
12520         },
12521
12522         /**
12523          * Selects an array of DOM nodes using JavaScript-only implementation.
12524          *
12525          * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
12526          *
12527          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
12528          * @param {HTMLElement/String} root (optional) The start of the query (defaults to document).
12529          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12530          * no matches, and empty Array is returned.
12531          */
12532         jsSelect: function(path, root, type){
12533             // set root to doc if not specified.
12534             root = root || document;
12535
12536             if(typeof root == "string"){
12537                 root = document.getElementById(root);
12538             }
12539             var paths = path.split(","),
12540                 results = [];
12541
12542             // loop over each selector
12543             for(var i = 0, len = paths.length; i < len; i++){
12544                 var subPath = paths[i].replace(trimRe, "");
12545                 // compile and place in cache
12546                 if(!cache[subPath]){
12547                     cache[subPath] = Ext.DomQuery.compile(subPath);
12548                     if(!cache[subPath]){
12549                         Ext.Error.raise({
12550                             sourceClass: 'Ext.DomQuery',
12551                             sourceMethod: 'jsSelect',
12552                             msg: subPath + ' is not a valid selector'
12553                         });
12554                     }
12555                 }
12556                 var result = cache[subPath](root);
12557                 if(result && result != document){
12558                     results = results.concat(result);
12559                 }
12560             }
12561
12562             // if there were multiple selectors, make sure dups
12563             // are eliminated
12564             if(paths.length > 1){
12565                 return nodup(results);
12566             }
12567             return results;
12568         },
12569
12570         isXml: function(el) {
12571             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
12572             return docEl ? docEl.nodeName !== "HTML" : false;
12573         },
12574
12575         /**
12576          * Selects an array of DOM nodes by CSS/XPath selector.
12577          *
12578          * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
12579          * {@link Ext.DomQuery#jsSelect} to do the work.
12580          *
12581          * Aliased as {@link Ext#query}.
12582          *
12583          * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
12584          *
12585          * @param {String} path The selector/xpath query
12586          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12587          * @return {HTMLElement[]} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
12588          * Empty array when no matches.
12589          * @method
12590          */
12591         select : document.querySelectorAll ? function(path, root, type) {
12592             root = root || document;
12593             /* 
12594              * Safari 3.x can't handle uppercase or unicode characters when in quirks mode.
12595              */
12596             if (!Ext.DomQuery.isXml(root) && !(Ext.isSafari3 && !Ext.isStrict)) { 
12597                 try {
12598                     /*
12599                      * This checking here is to "fix" the behaviour of querySelectorAll
12600                      * for non root document queries. The way qsa works is intentional,
12601                      * however it's definitely not the expected way it should work.
12602                      * More info: http://ejohn.org/blog/thoughts-on-queryselectorall/
12603                      *
12604                      * We only modify the path for single selectors (ie, no multiples),
12605                      * without a full parser it makes it difficult to do this correctly.
12606                      */
12607                     var isDocumentRoot = root.nodeType === 9,
12608                         _path = path,
12609                         _root = root;
12610
12611                     if (!isDocumentRoot && path.indexOf(',') === -1 && !startIdRe.test(path)) {
12612                         _path = '#' + Ext.id(root) + ' ' + path;
12613                         _root = root.parentNode;
12614                     }
12615                     return Ext.Array.toArray(_root.querySelectorAll(_path));
12616                 }
12617                 catch (e) {
12618                 }
12619             }
12620             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12621         } : function(path, root, type) {
12622             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12623         },
12624
12625         /**
12626          * Selects a single element.
12627          * @param {String} selector The selector/xpath query
12628          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12629          * @return {HTMLElement} The DOM element which matched the selector.
12630          */
12631         selectNode : function(path, root){
12632             return Ext.DomQuery.select(path, root)[0];
12633         },
12634
12635         /**
12636          * Selects the value of a node, optionally replacing null with the defaultValue.
12637          * @param {String} selector The selector/xpath query
12638          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12639          * @param {String} defaultValue (optional) When specified, this is return as empty value.
12640          * @return {String}
12641          */
12642         selectValue : function(path, root, defaultValue){
12643             path = path.replace(trimRe, "");
12644             if(!valueCache[path]){
12645                 valueCache[path] = Ext.DomQuery.compile(path, "select");
12646             }
12647             var n = valueCache[path](root), v;
12648             n = n[0] ? n[0] : n;
12649
12650             // overcome a limitation of maximum textnode size
12651             // Rumored to potentially crash IE6 but has not been confirmed.
12652             // http://reference.sitepoint.com/javascript/Node/normalize
12653             // https://developer.mozilla.org/En/DOM/Node.normalize
12654             if (typeof n.normalize == 'function') n.normalize();
12655
12656             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
12657             return ((v === null||v === undefined||v==='') ? defaultValue : v);
12658         },
12659
12660         /**
12661          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
12662          * @param {String} selector The selector/xpath query
12663          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12664          * @param {Number} defaultValue (optional) When specified, this is return as empty value.
12665          * @return {Number}
12666          */
12667         selectNumber : function(path, root, defaultValue){
12668             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
12669             return parseFloat(v);
12670         },
12671
12672         /**
12673          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
12674          * @param {String/HTMLElement/HTMLElement[]} el An element id, element or array of elements
12675          * @param {String} selector The simple selector to test
12676          * @return {Boolean}
12677          */
12678         is : function(el, ss){
12679             if(typeof el == "string"){
12680                 el = document.getElementById(el);
12681             }
12682             var isArray = Ext.isArray(el),
12683                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
12684             return isArray ? (result.length == el.length) : (result.length > 0);
12685         },
12686
12687         /**
12688          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
12689          * @param {HTMLElement[]} el An array of elements to filter
12690          * @param {String} selector The simple selector to test
12691          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
12692          * the selector instead of the ones that match
12693          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12694          * no matches, and empty Array is returned.
12695          */
12696         filter : function(els, ss, nonMatches){
12697             ss = ss.replace(trimRe, "");
12698             if(!simpleCache[ss]){
12699                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
12700             }
12701             var result = simpleCache[ss](els);
12702             return nonMatches ? quickDiff(result, els) : result;
12703         },
12704
12705         /**
12706          * Collection of matching regular expressions and code snippets.
12707          * Each capture group within () will be replace the {} in the select
12708          * statement as specified by their index.
12709          */
12710         matchers : [{
12711                 re: /^\.([\w-]+)/,
12712                 select: 'n = byClassName(n, " {1} ");'
12713             }, {
12714                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
12715                 select: 'n = byPseudo(n, "{1}", "{2}");'
12716             },{
12717                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
12718                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
12719             }, {
12720                 re: /^#([\w-]+)/,
12721                 select: 'n = byId(n, "{1}");'
12722             },{
12723                 re: /^@([\w-]+)/,
12724                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
12725             }
12726         ],
12727
12728         /**
12729          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
12730          * 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;.
12731          */
12732         operators : {
12733             "=" : function(a, v){
12734                 return a == v;
12735             },
12736             "!=" : function(a, v){
12737                 return a != v;
12738             },
12739             "^=" : function(a, v){
12740                 return a && a.substr(0, v.length) == v;
12741             },
12742             "$=" : function(a, v){
12743                 return a && a.substr(a.length-v.length) == v;
12744             },
12745             "*=" : function(a, v){
12746                 return a && a.indexOf(v) !== -1;
12747             },
12748             "%=" : function(a, v){
12749                 return (a % v) == 0;
12750             },
12751             "|=" : function(a, v){
12752                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
12753             },
12754             "~=" : function(a, v){
12755                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
12756             }
12757         },
12758
12759         /**
12760 Object hash of "pseudo class" filter functions which are used when filtering selections.
12761 Each function is passed two parameters:
12762
12763 - **c** : Array
12764     An Array of DOM elements to filter.
12765
12766 - **v** : String
12767     The argument (if any) supplied in the selector.
12768
12769 A filter function returns an Array of DOM elements which conform to the pseudo class.
12770 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
12771 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
12772
12773 For example, to filter `a` elements to only return links to __external__ resources:
12774
12775     Ext.DomQuery.pseudos.external = function(c, v){
12776         var r = [], ri = -1;
12777         for(var i = 0, ci; ci = c[i]; i++){
12778             // Include in result set only if it's a link to an external resource
12779             if(ci.hostname != location.hostname){
12780                 r[++ri] = ci;
12781             }
12782         }
12783         return r;
12784     };
12785
12786 Then external links could be gathered with the following statement:
12787
12788     var externalLinks = Ext.select("a:external");
12789
12790         * @markdown
12791         */
12792         pseudos : {
12793             "first-child" : function(c){
12794                 var r = [], ri = -1, n;
12795                 for(var i = 0, ci; ci = n = c[i]; i++){
12796                     while((n = n.previousSibling) && n.nodeType != 1);
12797                     if(!n){
12798                         r[++ri] = ci;
12799                     }
12800                 }
12801                 return r;
12802             },
12803
12804             "last-child" : function(c){
12805                 var r = [], ri = -1, n;
12806                 for(var i = 0, ci; ci = n = c[i]; i++){
12807                     while((n = n.nextSibling) && n.nodeType != 1);
12808                     if(!n){
12809                         r[++ri] = ci;
12810                     }
12811                 }
12812                 return r;
12813             },
12814
12815             "nth-child" : function(c, a) {
12816                 var r = [], ri = -1,
12817                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
12818                     f = (m[1] || 1) - 0, l = m[2] - 0;
12819                 for(var i = 0, n; n = c[i]; i++){
12820                     var pn = n.parentNode;
12821                     if (batch != pn._batch) {
12822                         var j = 0;
12823                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
12824                             if(cn.nodeType == 1){
12825                                cn.nodeIndex = ++j;
12826                             }
12827                         }
12828                         pn._batch = batch;
12829                     }
12830                     if (f == 1) {
12831                         if (l == 0 || n.nodeIndex == l){
12832                             r[++ri] = n;
12833                         }
12834                     } else if ((n.nodeIndex + l) % f == 0){
12835                         r[++ri] = n;
12836                     }
12837                 }
12838
12839                 return r;
12840             },
12841
12842             "only-child" : function(c){
12843                 var r = [], ri = -1;;
12844                 for(var i = 0, ci; ci = c[i]; i++){
12845                     if(!prev(ci) && !next(ci)){
12846                         r[++ri] = ci;
12847                     }
12848                 }
12849                 return r;
12850             },
12851
12852             "empty" : function(c){
12853                 var r = [], ri = -1;
12854                 for(var i = 0, ci; ci = c[i]; i++){
12855                     var cns = ci.childNodes, j = 0, cn, empty = true;
12856                     while(cn = cns[j]){
12857                         ++j;
12858                         if(cn.nodeType == 1 || cn.nodeType == 3){
12859                             empty = false;
12860                             break;
12861                         }
12862                     }
12863                     if(empty){
12864                         r[++ri] = ci;
12865                     }
12866                 }
12867                 return r;
12868             },
12869
12870             "contains" : function(c, v){
12871                 var r = [], ri = -1;
12872                 for(var i = 0, ci; ci = c[i]; i++){
12873                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
12874                         r[++ri] = ci;
12875                     }
12876                 }
12877                 return r;
12878             },
12879
12880             "nodeValue" : function(c, v){
12881                 var r = [], ri = -1;
12882                 for(var i = 0, ci; ci = c[i]; i++){
12883                     if(ci.firstChild && ci.firstChild.nodeValue == v){
12884                         r[++ri] = ci;
12885                     }
12886                 }
12887                 return r;
12888             },
12889
12890             "checked" : function(c){
12891                 var r = [], ri = -1;
12892                 for(var i = 0, ci; ci = c[i]; i++){
12893                     if(ci.checked == true){
12894                         r[++ri] = ci;
12895                     }
12896                 }
12897                 return r;
12898             },
12899
12900             "not" : function(c, ss){
12901                 return Ext.DomQuery.filter(c, ss, true);
12902             },
12903
12904             "any" : function(c, selectors){
12905                 var ss = selectors.split('|'),
12906                     r = [], ri = -1, s;
12907                 for(var i = 0, ci; ci = c[i]; i++){
12908                     for(var j = 0; s = ss[j]; j++){
12909                         if(Ext.DomQuery.is(ci, s)){
12910                             r[++ri] = ci;
12911                             break;
12912                         }
12913                     }
12914                 }
12915                 return r;
12916             },
12917
12918             "odd" : function(c){
12919                 return this["nth-child"](c, "odd");
12920             },
12921
12922             "even" : function(c){
12923                 return this["nth-child"](c, "even");
12924             },
12925
12926             "nth" : function(c, a){
12927                 return c[a-1] || [];
12928             },
12929
12930             "first" : function(c){
12931                 return c[0] || [];
12932             },
12933
12934             "last" : function(c){
12935                 return c[c.length-1] || [];
12936             },
12937
12938             "has" : function(c, ss){
12939                 var s = Ext.DomQuery.select,
12940                     r = [], ri = -1;
12941                 for(var i = 0, ci; ci = c[i]; i++){
12942                     if(s(ss, ci).length > 0){
12943                         r[++ri] = ci;
12944                     }
12945                 }
12946                 return r;
12947             },
12948
12949             "next" : function(c, ss){
12950                 var is = Ext.DomQuery.is,
12951                     r = [], ri = -1;
12952                 for(var i = 0, ci; ci = c[i]; i++){
12953                     var n = next(ci);
12954                     if(n && is(n, ss)){
12955                         r[++ri] = ci;
12956                     }
12957                 }
12958                 return r;
12959             },
12960
12961             "prev" : function(c, ss){
12962                 var is = Ext.DomQuery.is,
12963                     r = [], ri = -1;
12964                 for(var i = 0, ci; ci = c[i]; i++){
12965                     var n = prev(ci);
12966                     if(n && is(n, ss)){
12967                         r[++ri] = ci;
12968                     }
12969                 }
12970                 return r;
12971             }
12972         }
12973     };
12974 }();
12975
12976 /**
12977  * Shorthand of {@link Ext.DomQuery#select}
12978  * @member Ext
12979  * @method query
12980  * @alias Ext.DomQuery#select
12981  */
12982 Ext.query = Ext.DomQuery.select;
12983
12984 /**
12985  * @class Ext.Element
12986  * @alternateClassName Ext.core.Element
12987  *
12988  * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
12989  *
12990  * All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all
12991  * DOM elements.
12992  *
12993  * Note that the events documented in this class are not Ext events, they encapsulate browser events. Some older browsers
12994  * may not support the full range of events. Which events are supported is beyond the control of Ext JS.
12995  *
12996  * Usage:
12997  *
12998  *     // by id
12999  *     var el = Ext.get("my-div");
13000  *
13001  *     // by DOM element reference
13002  *     var el = Ext.get(myDivElement);
13003  *
13004  * # Animations
13005  *
13006  * When an element is manipulated, by default there is no animation.
13007  *
13008  *     var el = Ext.get("my-div");
13009  *
13010  *     // no animation
13011  *     el.setWidth(100);
13012  *
13013  * Many of the functions for manipulating an element have an optional "animate" parameter. This parameter can be
13014  * specified as boolean (true) for default animation effects.
13015  *
13016  *     // default animation
13017  *     el.setWidth(100, true);
13018  *
13019  * To configure the effects, an object literal with animation options to use as the Element animation configuration
13020  * object can also be specified. Note that the supported Element animation configuration options are a subset of the
13021  * {@link Ext.fx.Anim} animation options specific to Fx effects. The supported Element animation configuration options
13022  * are:
13023  *
13024  *     Option    Default   Description
13025  *     --------- --------  ---------------------------------------------
13026  *     {@link Ext.fx.Anim#duration duration}  .35       The duration of the animation in seconds
13027  *     {@link Ext.fx.Anim#easing easing}    easeOut   The easing method
13028  *     {@link Ext.fx.Anim#callback callback}  none      A function to execute when the anim completes
13029  *     {@link Ext.fx.Anim#scope scope}     this      The scope (this) of the callback function
13030  *
13031  * Usage:
13032  *
13033  *     // Element animation options object
13034  *     var opt = {
13035  *         {@link Ext.fx.Anim#duration duration}: 1,
13036  *         {@link Ext.fx.Anim#easing easing}: 'elasticIn',
13037  *         {@link Ext.fx.Anim#callback callback}: this.foo,
13038  *         {@link Ext.fx.Anim#scope scope}: this
13039  *     };
13040  *     // animation with some options set
13041  *     el.setWidth(100, opt);
13042  *
13043  * The Element animation object being used for the animation will be set on the options object as "anim", which allows
13044  * you to stop or manipulate the animation. Here is an example:
13045  *
13046  *     // using the "anim" property to get the Anim object
13047  *     if(opt.anim.isAnimated()){
13048  *         opt.anim.stop();
13049  *     }
13050  *
13051  * # Composite (Collections of) Elements
13052  *
13053  * For working with collections of Elements, see {@link Ext.CompositeElement}
13054  *
13055  * @constructor
13056  * Creates new Element directly.
13057  * @param {String/HTMLElement} element
13058  * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this
13059  * element in the cache and if there is it returns the same instance. This will skip that check (useful for extending
13060  * this class).
13061  * @return {Object}
13062  */
13063  (function() {
13064     var DOC = document,
13065         EC = Ext.cache;
13066
13067     Ext.Element = Ext.core.Element = function(element, forceNew) {
13068         var dom = typeof element == "string" ? DOC.getElementById(element) : element,
13069         id;
13070
13071         if (!dom) {
13072             return null;
13073         }
13074
13075         id = dom.id;
13076
13077         if (!forceNew && id && EC[id]) {
13078             // element object already exists
13079             return EC[id].el;
13080         }
13081
13082         /**
13083          * @property {HTMLElement} dom
13084          * The DOM element
13085          */
13086         this.dom = dom;
13087
13088         /**
13089          * @property {String} id
13090          * The DOM element ID
13091          */
13092         this.id = id || Ext.id(dom);
13093     };
13094
13095     var DH = Ext.DomHelper,
13096     El = Ext.Element;
13097
13098
13099     El.prototype = {
13100         /**
13101          * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
13102          * @param {Object} o The object with the attributes
13103          * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
13104          * @return {Ext.Element} this
13105          */
13106         set: function(o, useSet) {
13107             var el = this.dom,
13108                 attr,
13109                 val;
13110             useSet = (useSet !== false) && !!el.setAttribute;
13111
13112             for (attr in o) {
13113                 if (o.hasOwnProperty(attr)) {
13114                     val = o[attr];
13115                     if (attr == 'style') {
13116                         DH.applyStyles(el, val);
13117                     } else if (attr == 'cls') {
13118                         el.className = val;
13119                     } else if (useSet) {
13120                         el.setAttribute(attr, val);
13121                     } else {
13122                         el[attr] = val;
13123                     }
13124                 }
13125             }
13126             return this;
13127         },
13128
13129         //  Mouse events
13130         /**
13131          * @event click
13132          * Fires when a mouse click is detected within the element.
13133          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13134          * @param {HTMLElement} t The target of the event.
13135          */
13136         /**
13137          * @event contextmenu
13138          * Fires when a right click is detected within the element.
13139          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13140          * @param {HTMLElement} t The target of the event.
13141          */
13142         /**
13143          * @event dblclick
13144          * Fires when a mouse double click is detected within the element.
13145          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13146          * @param {HTMLElement} t The target of the event.
13147          */
13148         /**
13149          * @event mousedown
13150          * Fires when a mousedown is detected within the element.
13151          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13152          * @param {HTMLElement} t The target of the event.
13153          */
13154         /**
13155          * @event mouseup
13156          * Fires when a mouseup is detected within the element.
13157          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13158          * @param {HTMLElement} t The target of the event.
13159          */
13160         /**
13161          * @event mouseover
13162          * Fires when a mouseover is detected within the element.
13163          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13164          * @param {HTMLElement} t The target of the event.
13165          */
13166         /**
13167          * @event mousemove
13168          * Fires when a mousemove is detected with the element.
13169          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13170          * @param {HTMLElement} t The target of the event.
13171          */
13172         /**
13173          * @event mouseout
13174          * Fires when a mouseout is detected with the element.
13175          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13176          * @param {HTMLElement} t The target of the event.
13177          */
13178         /**
13179          * @event mouseenter
13180          * Fires when the mouse enters the element.
13181          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13182          * @param {HTMLElement} t The target of the event.
13183          */
13184         /**
13185          * @event mouseleave
13186          * Fires when the mouse leaves the element.
13187          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13188          * @param {HTMLElement} t The target of the event.
13189          */
13190
13191         //  Keyboard events
13192         /**
13193          * @event keypress
13194          * Fires when a keypress is detected within the element.
13195          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13196          * @param {HTMLElement} t The target of the event.
13197          */
13198         /**
13199          * @event keydown
13200          * Fires when a keydown is detected within the element.
13201          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13202          * @param {HTMLElement} t The target of the event.
13203          */
13204         /**
13205          * @event keyup
13206          * Fires when a keyup is detected within the element.
13207          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13208          * @param {HTMLElement} t The target of the event.
13209          */
13210
13211
13212         //  HTML frame/object events
13213         /**
13214          * @event load
13215          * Fires when the user agent finishes loading all content within the element. Only supported by window, frames,
13216          * objects and images.
13217          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13218          * @param {HTMLElement} t The target of the event.
13219          */
13220         /**
13221          * @event unload
13222          * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target
13223          * element or any of its content has been removed.
13224          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13225          * @param {HTMLElement} t The target of the event.
13226          */
13227         /**
13228          * @event abort
13229          * Fires when an object/image is stopped from loading before completely loaded.
13230          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13231          * @param {HTMLElement} t The target of the event.
13232          */
13233         /**
13234          * @event error
13235          * Fires when an object/image/frame cannot be loaded properly.
13236          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13237          * @param {HTMLElement} t The target of the event.
13238          */
13239         /**
13240          * @event resize
13241          * Fires when a document view is resized.
13242          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13243          * @param {HTMLElement} t The target of the event.
13244          */
13245         /**
13246          * @event scroll
13247          * Fires when a document view is scrolled.
13248          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13249          * @param {HTMLElement} t The target of the event.
13250          */
13251
13252         //  Form events
13253         /**
13254          * @event select
13255          * Fires when a user selects some text in a text field, including input and textarea.
13256          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13257          * @param {HTMLElement} t The target of the event.
13258          */
13259         /**
13260          * @event change
13261          * Fires when a control loses the input focus and its value has been modified since gaining focus.
13262          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13263          * @param {HTMLElement} t The target of the event.
13264          */
13265         /**
13266          * @event submit
13267          * Fires when a form is submitted.
13268          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13269          * @param {HTMLElement} t The target of the event.
13270          */
13271         /**
13272          * @event reset
13273          * Fires when a form is reset.
13274          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13275          * @param {HTMLElement} t The target of the event.
13276          */
13277         /**
13278          * @event focus
13279          * Fires when an element receives focus either via the pointing device or by tab navigation.
13280          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13281          * @param {HTMLElement} t The target of the event.
13282          */
13283         /**
13284          * @event blur
13285          * Fires when an element loses focus either via the pointing device or by tabbing navigation.
13286          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13287          * @param {HTMLElement} t The target of the event.
13288          */
13289
13290         //  User Interface events
13291         /**
13292          * @event DOMFocusIn
13293          * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
13294          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13295          * @param {HTMLElement} t The target of the event.
13296          */
13297         /**
13298          * @event DOMFocusOut
13299          * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
13300          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13301          * @param {HTMLElement} t The target of the event.
13302          */
13303         /**
13304          * @event DOMActivate
13305          * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
13306          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13307          * @param {HTMLElement} t The target of the event.
13308          */
13309
13310         //  DOM Mutation events
13311         /**
13312          * @event DOMSubtreeModified
13313          * Where supported. Fires when the subtree is modified.
13314          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13315          * @param {HTMLElement} t The target of the event.
13316          */
13317         /**
13318          * @event DOMNodeInserted
13319          * Where supported. Fires when a node has been added as a child of another node.
13320          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13321          * @param {HTMLElement} t The target of the event.
13322          */
13323         /**
13324          * @event DOMNodeRemoved
13325          * Where supported. Fires when a descendant node of the element is removed.
13326          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13327          * @param {HTMLElement} t The target of the event.
13328          */
13329         /**
13330          * @event DOMNodeRemovedFromDocument
13331          * Where supported. Fires when a node is being removed from a document.
13332          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13333          * @param {HTMLElement} t The target of the event.
13334          */
13335         /**
13336          * @event DOMNodeInsertedIntoDocument
13337          * Where supported. Fires when a node is being inserted into a document.
13338          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13339          * @param {HTMLElement} t The target of the event.
13340          */
13341         /**
13342          * @event DOMAttrModified
13343          * Where supported. Fires when an attribute has been modified.
13344          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13345          * @param {HTMLElement} t The target of the event.
13346          */
13347         /**
13348          * @event DOMCharacterDataModified
13349          * Where supported. Fires when the character data has been modified.
13350          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
13351          * @param {HTMLElement} t The target of the event.
13352          */
13353
13354         /**
13355          * @property {String} defaultUnit
13356          * The default unit to append to CSS values where a unit isn't provided.
13357          */
13358         defaultUnit: "px",
13359
13360         /**
13361          * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
13362          * @param {String} selector The simple selector to test
13363          * @return {Boolean} True if this element matches the selector, else false
13364          */
13365         is: function(simpleSelector) {
13366             return Ext.DomQuery.is(this.dom, simpleSelector);
13367         },
13368
13369         /**
13370          * Tries to focus the element. Any exceptions are caught and ignored.
13371          * @param {Number} defer (optional) Milliseconds to defer the focus
13372          * @return {Ext.Element} this
13373          */
13374         focus: function(defer,
13375                         /* private */
13376                         dom) {
13377             var me = this;
13378             dom = dom || me.dom;
13379             try {
13380                 if (Number(defer)) {
13381                     Ext.defer(me.focus, defer, null, [null, dom]);
13382                 } else {
13383                     dom.focus();
13384                 }
13385             } catch(e) {}
13386             return me;
13387         },
13388
13389         /**
13390          * Tries to blur the element. Any exceptions are caught and ignored.
13391          * @return {Ext.Element} this
13392          */
13393         blur: function() {
13394             try {
13395                 this.dom.blur();
13396             } catch(e) {}
13397             return this;
13398         },
13399
13400         /**
13401          * Returns the value of the "value" attribute
13402          * @param {Boolean} asNumber true to parse the value as a number
13403          * @return {String/Number}
13404          */
13405         getValue: function(asNumber) {
13406             var val = this.dom.value;
13407             return asNumber ? parseInt(val, 10) : val;
13408         },
13409
13410         /**
13411          * Appends an event handler to this element.
13412          *
13413          * @param {String} eventName The name of event to handle.
13414          *
13415          * @param {Function} fn The handler function the event invokes. This function is passed the following parameters:
13416          *
13417          * - **evt** : EventObject
13418          *
13419          *   The {@link Ext.EventObject EventObject} describing the event.
13420          *
13421          * - **el** : HtmlElement
13422          *
13423          *   The DOM element which was the target of the event. Note that this may be filtered by using the delegate option.
13424          *
13425          * - **o** : Object
13426          *
13427          *   The options object from the addListener call.
13428          *
13429          * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed. **If
13430          * omitted, defaults to this Element.**
13431          *
13432          * @param {Object} options (optional) An object containing handler configuration properties. This may contain any of
13433          * the following properties:
13434          *
13435          * - **scope** Object :
13436          *
13437          *   The scope (**this** reference) in which the handler function is executed. **If omitted, defaults to this
13438          *   Element.**
13439          *
13440          * - **delegate** String:
13441          *
13442          *   A simple selector to filter the target or look for a descendant of the target. See below for additional details.
13443          *
13444          * - **stopEvent** Boolean:
13445          *
13446          *   True to stop the event. That is stop propagation, and prevent the default action.
13447          *
13448          * - **preventDefault** Boolean:
13449          *
13450          *   True to prevent the default action
13451          *
13452          * - **stopPropagation** Boolean:
13453          *
13454          *   True to prevent event propagation
13455          *
13456          * - **normalized** Boolean:
13457          *
13458          *   False to pass a browser event to the handler function instead of an Ext.EventObject
13459          *
13460          * - **target** Ext.Element:
13461          *
13462          *   Only call the handler if the event was fired on the target Element, _not_ if the event was bubbled up from a
13463          *   child node.
13464          *
13465          * - **delay** Number:
13466          *
13467          *   The number of milliseconds to delay the invocation of the handler after the event fires.
13468          *
13469          * - **single** Boolean:
13470          *
13471          *   True to add a handler to handle just the next firing of the event, and then remove itself.
13472          *
13473          * - **buffer** Number:
13474          *
13475          *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
13476          *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
13477          *   handler is scheduled in its place.
13478          *
13479          * **Combining Options**
13480          *
13481          * In the following examples, the shorthand form {@link #on} is used rather than the more verbose addListener. The
13482          * two are equivalent. Using the options argument, it is possible to combine different types of listeners:
13483          *
13484          * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the options
13485          * object. The options object is available as the third parameter in the handler function.
13486          *
13487          * Code:
13488          *
13489          *     el.on('click', this.onClick, this, {
13490          *         single: true,
13491          *         delay: 100,
13492          *         stopEvent : true,
13493          *         forumId: 4
13494          *     });
13495          *
13496          * **Attaching multiple handlers in 1 call**
13497          *
13498          * The method also allows for a single argument to be passed which is a config object containing properties which
13499          * specify multiple handlers.
13500          *
13501          * Code:
13502          *
13503          *     el.on({
13504          *         'click' : {
13505          *             fn: this.onClick,
13506          *             scope: this,
13507          *             delay: 100
13508          *         },
13509          *         'mouseover' : {
13510          *             fn: this.onMouseOver,
13511          *             scope: this
13512          *         },
13513          *         'mouseout' : {
13514          *             fn: this.onMouseOut,
13515          *             scope: this
13516          *         }
13517          *     });
13518          *
13519          * Or a shorthand syntax:
13520          *
13521          * Code:
13522          *
13523          *     el.on({
13524          *         'click' : this.onClick,
13525          *         'mouseover' : this.onMouseOver,
13526          *         'mouseout' : this.onMouseOut,
13527          *         scope: this
13528          *     });
13529          *
13530          * **delegate**
13531          *
13532          * This is a configuration option that you can pass along when registering a handler for an event to assist with
13533          * event delegation. Event delegation is a technique that is used to reduce memory consumption and prevent exposure
13534          * to memory-leaks. By registering an event for a container element as opposed to each element within a container.
13535          * By setting this configuration option to a simple selector, the target element will be filtered to look for a
13536          * descendant of the target. For example:
13537          *
13538          *     // using this markup:
13539          *     <div id='elId'>
13540          *         <p id='p1'>paragraph one</p>
13541          *         <p id='p2' class='clickable'>paragraph two</p>
13542          *         <p id='p3'>paragraph three</p>
13543          *     </div>
13544          *
13545          *     // utilize event delegation to registering just one handler on the container element:
13546          *     el = Ext.get('elId');
13547          *     el.on(
13548          *         'click',
13549          *         function(e,t) {
13550          *             // handle click
13551          *             console.info(t.id); // 'p2'
13552          *         },
13553          *         this,
13554          *         {
13555          *             // filter the target element to be a descendant with the class 'clickable'
13556          *             delegate: '.clickable'
13557          *         }
13558          *     );
13559          *
13560          * @return {Ext.Element} this
13561          */
13562         addListener: function(eventName, fn, scope, options) {
13563             Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
13564             return this;
13565         },
13566
13567         /**
13568          * Removes an event handler from this element.
13569          *
13570          * **Note**: if a *scope* was explicitly specified when {@link #addListener adding} the listener,
13571          * the same scope must be specified here.
13572          *
13573          * Example:
13574          *
13575          *     el.removeListener('click', this.handlerFn);
13576          *     // or
13577          *     el.un('click', this.handlerFn);
13578          *
13579          * @param {String} eventName The name of the event from which to remove the handler.
13580          * @param {Function} fn The handler function to remove. **This must be a reference to the function passed into the
13581          * {@link #addListener} call.**
13582          * @param {Object} scope If a scope (**this** reference) was specified when the listener was added, then this must
13583          * refer to the same object.
13584          * @return {Ext.Element} this
13585          */
13586         removeListener: function(eventName, fn, scope) {
13587             Ext.EventManager.un(this.dom, eventName, fn, scope || this);
13588             return this;
13589         },
13590
13591         /**
13592          * Removes all previous added listeners from this element
13593          * @return {Ext.Element} this
13594          */
13595         removeAllListeners: function() {
13596             Ext.EventManager.removeAll(this.dom);
13597             return this;
13598         },
13599
13600         /**
13601          * Recursively removes all previous added listeners from this element and its children
13602          * @return {Ext.Element} this
13603          */
13604         purgeAllListeners: function() {
13605             Ext.EventManager.purgeElement(this);
13606             return this;
13607         },
13608
13609         /**
13610          * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
13611          * @param size {Mixed} The size to set
13612          * @param units {String} The units to append to a numeric size value
13613          * @private
13614          */
13615         addUnits: function(size, units) {
13616
13617             // Most common case first: Size is set to a number
13618             if (Ext.isNumber(size)) {
13619                 return size + (units || this.defaultUnit || 'px');
13620             }
13621
13622             // Size set to a value which means "auto"
13623             if (size === "" || size == "auto" || size == null) {
13624                 return size || '';
13625             }
13626
13627             // Otherwise, warn if it's not a valid CSS measurement
13628             if (!unitPattern.test(size)) {
13629                 if (Ext.isDefined(Ext.global.console)) {
13630                     Ext.global.console.warn("Warning, size detected as NaN on Element.addUnits.");
13631                 }
13632                 return size || '';
13633             }
13634             return size;
13635         },
13636
13637         /**
13638          * Tests various css rules/browsers to determine if this element uses a border box
13639          * @return {Boolean}
13640          */
13641         isBorderBox: function() {
13642             return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
13643         },
13644
13645         /**
13646          * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13647          * Ext.removeNode}
13648          */
13649         remove: function() {
13650             var me = this,
13651             dom = me.dom;
13652
13653             if (dom) {
13654                 delete me.dom;
13655                 Ext.removeNode(dom);
13656             }
13657         },
13658
13659         /**
13660          * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
13661          * @param {Function} overFn The function to call when the mouse enters the Element.
13662          * @param {Function} outFn The function to call when the mouse leaves the Element.
13663          * @param {Object} scope (optional) The scope (`this` reference) in which the functions are executed. Defaults
13664          * to the Element's DOM element.
13665          * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the
13666          * options parameter}.
13667          * @return {Ext.Element} this
13668          */
13669         hover: function(overFn, outFn, scope, options) {
13670             var me = this;
13671             me.on('mouseenter', overFn, scope || me.dom, options);
13672             me.on('mouseleave', outFn, scope || me.dom, options);
13673             return me;
13674         },
13675
13676         /**
13677          * Returns true if this element is an ancestor of the passed element
13678          * @param {HTMLElement/String} el The element to check
13679          * @return {Boolean} True if this element is an ancestor of el, else false
13680          */
13681         contains: function(el) {
13682             return ! el ? false: Ext.Element.isAncestor(this.dom, el.dom ? el.dom: el);
13683         },
13684
13685         /**
13686          * Returns the value of a namespaced attribute from the element's underlying DOM node.
13687          * @param {String} namespace The namespace in which to look for the attribute
13688          * @param {String} name The attribute name
13689          * @return {String} The attribute value
13690          */
13691         getAttributeNS: function(ns, name) {
13692             return this.getAttribute(name, ns);
13693         },
13694
13695         /**
13696          * Returns the value of an attribute from the element's underlying DOM node.
13697          * @param {String} name The attribute name
13698          * @param {String} namespace (optional) The namespace in which to look for the attribute
13699          * @return {String} The attribute value
13700          * @method
13701          */
13702         getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
13703         function(name, ns) {
13704             var d = this.dom,
13705             type;
13706             if(ns) {
13707                 type = typeof d[ns + ":" + name];
13708                 if (type != 'undefined' && type != 'unknown') {
13709                     return d[ns + ":" + name] || null;
13710                 }
13711                 return null;
13712             }
13713             if (name === "for") {
13714                 name = "htmlFor";
13715             }
13716             return d[name] || null;
13717         }: function(name, ns) {
13718             var d = this.dom;
13719             if (ns) {
13720                return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
13721             }
13722             return  d.getAttribute(name) || d[name] || null;
13723         },
13724
13725         /**
13726          * Update the innerHTML of this element
13727          * @param {String} html The new HTML
13728          * @return {Ext.Element} this
13729          */
13730         update: function(html) {
13731             if (this.dom) {
13732                 this.dom.innerHTML = html;
13733             }
13734             return this;
13735         }
13736     };
13737
13738     var ep = El.prototype;
13739
13740     El.addMethods = function(o) {
13741         Ext.apply(ep, o);
13742     };
13743
13744     /**
13745      * @method
13746      * @alias Ext.Element#addListener
13747      * Shorthand for {@link #addListener}.
13748      */
13749     ep.on = ep.addListener;
13750
13751     /**
13752      * @method
13753      * @alias Ext.Element#removeListener
13754      * Shorthand for {@link #removeListener}.
13755      */
13756     ep.un = ep.removeListener;
13757
13758     /**
13759      * @method
13760      * @alias Ext.Element#removeAllListeners
13761      * Alias for {@link #removeAllListeners}.
13762      */
13763     ep.clearListeners = ep.removeAllListeners;
13764
13765     /**
13766      * @method destroy
13767      * @member Ext.Element
13768      * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13769      * Ext.removeNode}. Alias to {@link #remove}.
13770      */
13771     ep.destroy = ep.remove;
13772
13773     /**
13774      * @property {Boolean} autoBoxAdjust
13775      * true to automatically adjust width and height settings for box-model issues (default to true)
13776      */
13777     ep.autoBoxAdjust = true;
13778
13779     // private
13780     var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
13781     docEl;
13782
13783     /**
13784      * Retrieves Ext.Element objects. {@link Ext#get} is an alias for {@link Ext.Element#get}.
13785      *
13786      * **This method does not retrieve {@link Ext.Component Component}s.** This method retrieves Ext.Element
13787      * objects which encapsulate DOM elements. To retrieve a Component by its ID, use {@link Ext.ComponentManager#get}.
13788      *
13789      * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
13790      * the same id via AJAX or DOM.
13791      *
13792      * @param {String/HTMLElement/Ext.Element} el The id of the node, a DOM Node or an existing Element.
13793      * @return {Ext.Element} The Element object (or null if no matching element was found)
13794      * @static
13795      */
13796     El.get = function(el) {
13797         var ex,
13798         elm,
13799         id;
13800         if (!el) {
13801             return null;
13802         }
13803         if (typeof el == "string") {
13804             // element id
13805             if (! (elm = DOC.getElementById(el))) {
13806                 return null;
13807             }
13808             if (EC[el] && EC[el].el) {
13809                 ex = EC[el].el;
13810                 ex.dom = elm;
13811             } else {
13812                 ex = El.addToCache(new El(elm));
13813             }
13814             return ex;
13815         } else if (el.tagName) {
13816             // dom element
13817             if (! (id = el.id)) {
13818                 id = Ext.id(el);
13819             }
13820             if (EC[id] && EC[id].el) {
13821                 ex = EC[id].el;
13822                 ex.dom = el;
13823             } else {
13824                 ex = El.addToCache(new El(el));
13825             }
13826             return ex;
13827         } else if (el instanceof El) {
13828             if (el != docEl) {
13829                 // refresh dom element in case no longer valid,
13830                 // catch case where it hasn't been appended
13831                 // If an el instance is passed, don't pass to getElementById without some kind of id
13832                 if (Ext.isIE && (el.id == undefined || el.id == '')) {
13833                     el.dom = el.dom;
13834                 } else {
13835                     el.dom = DOC.getElementById(el.id) || el.dom;
13836                 }
13837             }
13838             return el;
13839         } else if (el.isComposite) {
13840             return el;
13841         } else if (Ext.isArray(el)) {
13842             return El.select(el);
13843         } else if (el == DOC) {
13844             // create a bogus element object representing the document object
13845             if (!docEl) {
13846                 var f = function() {};
13847                 f.prototype = El.prototype;
13848                 docEl = new f();
13849                 docEl.dom = DOC;
13850             }
13851             return docEl;
13852         }
13853         return null;
13854     };
13855
13856     /**
13857      * Retrieves Ext.Element objects like {@link Ext#get} but is optimized for sub-elements.
13858      * This is helpful for performance, because in IE (prior to IE 9), `getElementById` uses
13859      * an non-optimized search. In those browsers, starting the search for an element with a
13860      * matching ID at a parent of that element will greatly speed up the process.
13861      *
13862      * Unlike {@link Ext#get}, this method only accepts ID's. If the ID is not a child of
13863      * this element, it will still be found if it exists in the document, but will be slower
13864      * than calling {@link Ext#get} directly.
13865      *
13866      * @param {String} id The id of the element to get.
13867      * @return {Ext.Element} The Element object (or null if no matching element was found)
13868      * @member Ext.Element
13869      * @method getById
13870      * @markdown
13871      */
13872     ep.getById = (!Ext.isIE6 && !Ext.isIE7 && !Ext.isIE8) ? El.get :
13873         function (id) {
13874             var dom = this.dom,
13875                 cached, el, ret;
13876
13877             if (dom) {
13878                 el = dom.all[id];
13879                 if (el) {
13880                     // calling El.get here is a real hit (2x slower) because it has to
13881                     // redetermine that we are giving it a dom el.
13882                     cached = EC[id];
13883                     if (cached && cached.el) {
13884                         ret = cached.el;
13885                         ret.dom = el;
13886                     } else {
13887                         ret = El.addToCache(new El(el));
13888                     }
13889                     return ret;
13890                 }
13891             }
13892
13893             return El.get(id);
13894         };
13895
13896     El.addToCache = function(el, id) {
13897         if (el) {
13898             id = id || el.id;
13899             EC[id] = {
13900                 el: el,
13901                 data: {},
13902                 events: {}
13903             };
13904         }
13905         return el;
13906     };
13907
13908     // private method for getting and setting element data
13909     El.data = function(el, key, value) {
13910         el = El.get(el);
13911         if (!el) {
13912             return null;
13913         }
13914         var c = EC[el.id].data;
13915         if (arguments.length == 2) {
13916             return c[key];
13917         } else {
13918             return (c[key] = value);
13919         }
13920     };
13921
13922     // private
13923     // Garbage collection - uncache elements/purge listeners on orphaned elements
13924     // so we don't hold a reference and cause the browser to retain them
13925     function garbageCollect() {
13926         if (!Ext.enableGarbageCollector) {
13927             clearInterval(El.collectorThreadId);
13928         } else {
13929             var eid,
13930             el,
13931             d,
13932             o;
13933
13934             for (eid in EC) {
13935                 if (!EC.hasOwnProperty(eid)) {
13936                     continue;
13937                 }
13938                 o = EC[eid];
13939                 if (o.skipGarbageCollection) {
13940                     continue;
13941                 }
13942                 el = o.el;
13943                 d = el.dom;
13944                 // -------------------------------------------------------
13945                 // Determining what is garbage:
13946                 // -------------------------------------------------------
13947                 // !d
13948                 // dom node is null, definitely garbage
13949                 // -------------------------------------------------------
13950                 // !d.parentNode
13951                 // no parentNode == direct orphan, definitely garbage
13952                 // -------------------------------------------------------
13953                 // !d.offsetParent && !document.getElementById(eid)
13954                 // display none elements have no offsetParent so we will
13955                 // also try to look it up by it's id. However, check
13956                 // offsetParent first so we don't do unneeded lookups.
13957                 // This enables collection of elements that are not orphans
13958                 // directly, but somewhere up the line they have an orphan
13959                 // parent.
13960                 // -------------------------------------------------------
13961                 if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
13962                     if (d && Ext.enableListenerCollection) {
13963                         Ext.EventManager.removeAll(d);
13964                     }
13965                     delete EC[eid];
13966                 }
13967             }
13968             // Cleanup IE Object leaks
13969             if (Ext.isIE) {
13970                 var t = {};
13971                 for (eid in EC) {
13972                     if (!EC.hasOwnProperty(eid)) {
13973                         continue;
13974                     }
13975                     t[eid] = EC[eid];
13976                 }
13977                 EC = Ext.cache = t;
13978             }
13979         }
13980     }
13981     El.collectorThreadId = setInterval(garbageCollect, 30000);
13982
13983     var flyFn = function() {};
13984     flyFn.prototype = El.prototype;
13985
13986     // dom is optional
13987     El.Flyweight = function(dom) {
13988         this.dom = dom;
13989     };
13990
13991     El.Flyweight.prototype = new flyFn();
13992     El.Flyweight.prototype.isFlyweight = true;
13993     El._flyweights = {};
13994
13995     /**
13996      * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
13997      * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
13998      * {@link Ext.Element#fly}.
13999      *
14000      * Use this to make one-time references to DOM elements which are not going to be accessed again either by
14001      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
14002      * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the Ext.Element
14003      * class.
14004      *
14005      * @param {String/HTMLElement} el The dom node or id
14006      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts (e.g.
14007      * internally Ext uses "_global")
14008      * @return {Ext.Element} The shared Element object (or null if no matching element was found)
14009      * @static
14010      */
14011     El.fly = function(el, named) {
14012         var ret = null;
14013         named = named || '_global';
14014         el = Ext.getDom(el);
14015         if (el) {
14016             (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
14017             ret = El._flyweights[named];
14018         }
14019         return ret;
14020     };
14021
14022     /**
14023      * @member Ext
14024      * @method get
14025      * @alias Ext.Element#get
14026      */
14027     Ext.get = El.get;
14028
14029     /**
14030      * @member Ext
14031      * @method fly
14032      * @alias Ext.Element#fly
14033      */
14034     Ext.fly = El.fly;
14035
14036     // speedy lookup for elements never to box adjust
14037     var noBoxAdjust = Ext.isStrict ? {
14038         select: 1
14039     }: {
14040         input: 1,
14041         select: 1,
14042         textarea: 1
14043     };
14044     if (Ext.isIE || Ext.isGecko) {
14045         noBoxAdjust['button'] = 1;
14046     }
14047 })();
14048
14049 /**
14050  * @class Ext.Element
14051  */
14052 Ext.Element.addMethods({
14053     /**
14054      * 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)
14055      * @param {String} selector The simple selector to test
14056      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
14057      * The max depth to search as a number or element (defaults to 50 || document.body)
14058      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
14059      * @return {HTMLElement} The matching DOM node (or null if no match was found)
14060      */
14061     findParent : function(simpleSelector, maxDepth, returnEl) {
14062         var p = this.dom,
14063             b = document.body,
14064             depth = 0,
14065             stopEl;
14066
14067         maxDepth = maxDepth || 50;
14068         if (isNaN(maxDepth)) {
14069             stopEl = Ext.getDom(maxDepth);
14070             maxDepth = Number.MAX_VALUE;
14071         }
14072         while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
14073             if (Ext.DomQuery.is(p, simpleSelector)) {
14074                 return returnEl ? Ext.get(p) : p;
14075             }
14076             depth++;
14077             p = p.parentNode;
14078         }
14079         return null;
14080     },
14081
14082     /**
14083      * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
14084      * @param {String} selector The simple selector to test
14085      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
14086      * The max depth to search as a number or element (defaults to 10 || document.body)
14087      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
14088      * @return {HTMLElement} The matching DOM node (or null if no match was found)
14089      */
14090     findParentNode : function(simpleSelector, maxDepth, returnEl) {
14091         var p = Ext.fly(this.dom.parentNode, '_internal');
14092         return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
14093     },
14094
14095     /**
14096      * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
14097      * This is a shortcut for findParentNode() that always returns an Ext.Element.
14098      * @param {String} selector The simple selector to test
14099      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
14100      * The max depth to search as a number or element (defaults to 10 || document.body)
14101      * @return {Ext.Element} The matching DOM node (or null if no match was found)
14102      */
14103     up : function(simpleSelector, maxDepth) {
14104         return this.findParentNode(simpleSelector, maxDepth, true);
14105     },
14106
14107     /**
14108      * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
14109      * @param {String} selector The CSS selector
14110      * @return {Ext.CompositeElement/Ext.CompositeElement} The composite element
14111      */
14112     select : function(selector) {
14113         return Ext.Element.select(selector, false,  this.dom);
14114     },
14115
14116     /**
14117      * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
14118      * @param {String} selector The CSS selector
14119      * @return {HTMLElement[]} An array of the matched nodes
14120      */
14121     query : function(selector) {
14122         return Ext.DomQuery.select(selector, this.dom);
14123     },
14124
14125     /**
14126      * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
14127      * @param {String} selector The CSS selector
14128      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
14129      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
14130      */
14131     down : function(selector, returnDom) {
14132         var n = Ext.DomQuery.selectNode(selector, this.dom);
14133         return returnDom ? n : Ext.get(n);
14134     },
14135
14136     /**
14137      * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
14138      * @param {String} selector The CSS selector
14139      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
14140      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
14141      */
14142     child : function(selector, returnDom) {
14143         var node,
14144             me = this,
14145             id;
14146         id = Ext.get(me).id;
14147         // Escape . or :
14148         id = id.replace(/[\.:]/g, "\\$0");
14149         node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
14150         return returnDom ? node : Ext.get(node);
14151     },
14152
14153      /**
14154      * Gets the parent node for this element, optionally chaining up trying to match a selector
14155      * @param {String} selector (optional) Find a parent node that matches the passed simple selector
14156      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14157      * @return {Ext.Element/HTMLElement} The parent node or null
14158      */
14159     parent : function(selector, returnDom) {
14160         return this.matchNode('parentNode', 'parentNode', selector, returnDom);
14161     },
14162
14163      /**
14164      * Gets the next sibling, skipping text nodes
14165      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
14166      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14167      * @return {Ext.Element/HTMLElement} The next sibling or null
14168      */
14169     next : function(selector, returnDom) {
14170         return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
14171     },
14172
14173     /**
14174      * Gets the previous sibling, skipping text nodes
14175      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
14176      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14177      * @return {Ext.Element/HTMLElement} The previous sibling or null
14178      */
14179     prev : function(selector, returnDom) {
14180         return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
14181     },
14182
14183
14184     /**
14185      * Gets the first child, skipping text nodes
14186      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
14187      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14188      * @return {Ext.Element/HTMLElement} The first child or null
14189      */
14190     first : function(selector, returnDom) {
14191         return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
14192     },
14193
14194     /**
14195      * Gets the last child, skipping text nodes
14196      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
14197      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
14198      * @return {Ext.Element/HTMLElement} The last child or null
14199      */
14200     last : function(selector, returnDom) {
14201         return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
14202     },
14203
14204     matchNode : function(dir, start, selector, returnDom) {
14205         if (!this.dom) {
14206             return null;
14207         }
14208
14209         var n = this.dom[start];
14210         while (n) {
14211             if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
14212                 return !returnDom ? Ext.get(n) : n;
14213             }
14214             n = n[dir];
14215         }
14216         return null;
14217     }
14218 });
14219
14220 /**
14221  * @class Ext.Element
14222  */
14223 Ext.Element.addMethods({
14224     /**
14225      * Appends the passed element(s) to this element
14226      * @param {String/HTMLElement/Ext.Element} el
14227      * The id of the node, a DOM Node or an existing Element.
14228      * @return {Ext.Element} this
14229      */
14230     appendChild : function(el) {
14231         return Ext.get(el).appendTo(this);
14232     },
14233
14234     /**
14235      * Appends this element to the passed element
14236      * @param {String/HTMLElement/Ext.Element} el The new parent element.
14237      * The id of the node, a DOM Node or an existing Element.
14238      * @return {Ext.Element} this
14239      */
14240     appendTo : function(el) {
14241         Ext.getDom(el).appendChild(this.dom);
14242         return this;
14243     },
14244
14245     /**
14246      * Inserts this element before the passed element in the DOM
14247      * @param {String/HTMLElement/Ext.Element} el The element before which this element will be inserted.
14248      * The id of the node, a DOM Node or an existing Element.
14249      * @return {Ext.Element} this
14250      */
14251     insertBefore : function(el) {
14252         el = Ext.getDom(el);
14253         el.parentNode.insertBefore(this.dom, el);
14254         return this;
14255     },
14256
14257     /**
14258      * Inserts this element after the passed element in the DOM
14259      * @param {String/HTMLElement/Ext.Element} el The element to insert after.
14260      * The id of the node, a DOM Node or an existing Element.
14261      * @return {Ext.Element} this
14262      */
14263     insertAfter : function(el) {
14264         el = Ext.getDom(el);
14265         el.parentNode.insertBefore(this.dom, el.nextSibling);
14266         return this;
14267     },
14268
14269     /**
14270      * Inserts (or creates) an element (or DomHelper config) as the first child of this element
14271      * @param {String/HTMLElement/Ext.Element/Object} el The id or element to insert or a DomHelper config
14272      * to create and insert
14273      * @return {Ext.Element} The new child
14274      */
14275     insertFirst : function(el, returnDom) {
14276         el = el || {};
14277         if (el.nodeType || el.dom || typeof el == 'string') { // element
14278             el = Ext.getDom(el);
14279             this.dom.insertBefore(el, this.dom.firstChild);
14280             return !returnDom ? Ext.get(el) : el;
14281         }
14282         else { // dh config
14283             return this.createChild(el, this.dom.firstChild, returnDom);
14284         }
14285     },
14286
14287     /**
14288      * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
14289      * @param {String/HTMLElement/Ext.Element/Object/Array} el The id, element to insert or a DomHelper config
14290      * to create and insert *or* an array of any of those.
14291      * @param {String} where (optional) 'before' or 'after' defaults to before
14292      * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.Element
14293      * @return {Ext.Element} The inserted Element. If an array is passed, the last inserted element is returned.
14294      */
14295     insertSibling: function(el, where, returnDom){
14296         var me = this, rt,
14297         isAfter = (where || 'before').toLowerCase() == 'after',
14298         insertEl;
14299
14300         if(Ext.isArray(el)){
14301             insertEl = me;
14302             Ext.each(el, function(e) {
14303                 rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
14304                 if(isAfter){
14305                     insertEl = rt;
14306                 }
14307             });
14308             return rt;
14309         }
14310
14311         el = el || {};
14312
14313         if(el.nodeType || el.dom){
14314             rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
14315             if (!returnDom) {
14316                 rt = Ext.get(rt);
14317             }
14318         }else{
14319             if (isAfter && !me.dom.nextSibling) {
14320                 rt = Ext.DomHelper.append(me.dom.parentNode, el, !returnDom);
14321             } else {
14322                 rt = Ext.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
14323             }
14324         }
14325         return rt;
14326     },
14327
14328     /**
14329      * Replaces the passed element with this element
14330      * @param {String/HTMLElement/Ext.Element} el The element to replace.
14331      * The id of the node, a DOM Node or an existing Element.
14332      * @return {Ext.Element} this
14333      */
14334     replace : function(el) {
14335         el = Ext.get(el);
14336         this.insertBefore(el);
14337         el.remove();
14338         return this;
14339     },
14340     
14341     /**
14342      * Replaces this element with the passed element
14343      * @param {String/HTMLElement/Ext.Element/Object} el The new element (id of the node, a DOM Node
14344      * or an existing Element) or a DomHelper config of an element to create
14345      * @return {Ext.Element} this
14346      */
14347     replaceWith: function(el){
14348         var me = this;
14349             
14350         if(el.nodeType || el.dom || typeof el == 'string'){
14351             el = Ext.get(el);
14352             me.dom.parentNode.insertBefore(el, me.dom);
14353         }else{
14354             el = Ext.DomHelper.insertBefore(me.dom, el);
14355         }
14356         
14357         delete Ext.cache[me.id];
14358         Ext.removeNode(me.dom);      
14359         me.id = Ext.id(me.dom = el);
14360         Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);     
14361         return me;
14362     },
14363     
14364     /**
14365      * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
14366      * @param {Object} config DomHelper element config object.  If no tag is specified (e.g., {tag:'input'}) then a div will be
14367      * automatically generated with the specified attributes.
14368      * @param {HTMLElement} insertBefore (optional) a child element of this element
14369      * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
14370      * @return {Ext.Element} The new child element
14371      */
14372     createChild : function(config, insertBefore, returnDom) {
14373         config = config || {tag:'div'};
14374         if (insertBefore) {
14375             return Ext.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
14376         }
14377         else {
14378             return Ext.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config,  returnDom !== true);
14379         }
14380     },
14381
14382     /**
14383      * Creates and wraps this element with another element
14384      * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
14385      * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
14386      * @return {HTMLElement/Ext.Element} The newly created wrapper element
14387      */
14388     wrap : function(config, returnDom) {
14389         var newEl = Ext.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
14390             d = newEl.dom || newEl;
14391
14392         d.appendChild(this.dom);
14393         return newEl;
14394     },
14395
14396     /**
14397      * Inserts an html fragment into this element
14398      * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
14399      * See {@link Ext.DomHelper#insertHtml} for details.
14400      * @param {String} html The HTML fragment
14401      * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
14402      * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
14403      */
14404     insertHtml : function(where, html, returnEl) {
14405         var el = Ext.DomHelper.insertHtml(where, this.dom, html);
14406         return returnEl ? Ext.get(el) : el;
14407     }
14408 });
14409
14410 /**
14411  * @class Ext.Element
14412  */
14413 (function(){
14414     // local style camelizing for speed
14415     var ELEMENT = Ext.Element,
14416         supports = Ext.supports,
14417         view = document.defaultView,
14418         opacityRe = /alpha\(opacity=(.*)\)/i,
14419         trimRe = /^\s+|\s+$/g,
14420         spacesRe = /\s+/,
14421         wordsRe = /\w/g,
14422         adjustDirect2DTableRe = /table-row|table-.*-group/,
14423         INTERNAL = '_internal',
14424         PADDING = 'padding',
14425         MARGIN = 'margin',
14426         BORDER = 'border',
14427         LEFT = '-left',
14428         RIGHT = '-right',
14429         TOP = '-top',
14430         BOTTOM = '-bottom',
14431         WIDTH = '-width',
14432         MATH = Math,
14433         HIDDEN = 'hidden',
14434         ISCLIPPED = 'isClipped',
14435         OVERFLOW = 'overflow',
14436         OVERFLOWX = 'overflow-x',
14437         OVERFLOWY = 'overflow-y',
14438         ORIGINALCLIP = 'originalClip',
14439         // special markup used throughout Ext when box wrapping elements
14440         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
14441         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
14442         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
14443         data = ELEMENT.data;
14444
14445     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>';
14446
14447     // These property values are read from the parentNode if they cannot be read
14448     // from the child:
14449     ELEMENT.inheritedProps = {
14450         fontSize: 1,
14451         fontStyle: 1,
14452         opacity: 1
14453     };
14454
14455     Ext.override(ELEMENT, {
14456
14457         /**
14458          * TODO: Look at this
14459          */
14460         // private  ==> used by Fx
14461         adjustWidth : function(width) {
14462             var me = this,
14463                 isNum = (typeof width == 'number');
14464
14465             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14466                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14467             }
14468             return (isNum && width < 0) ? 0 : width;
14469         },
14470
14471         // private   ==> used by Fx
14472         adjustHeight : function(height) {
14473             var me = this,
14474                 isNum = (typeof height == "number");
14475
14476             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14477                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14478             }
14479             return (isNum && height < 0) ? 0 : height;
14480         },
14481
14482
14483         /**
14484          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
14485          * @param {String/String[]} className The CSS classes to add separated by space, or an array of classes
14486          * @return {Ext.Element} this
14487          */
14488         addCls : function(className){
14489             var me = this,
14490                 cls = [],
14491                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
14492                 i, len, v;
14493             if (className === undefined) {
14494                 return me;
14495             }
14496             // Separate case is for speed
14497             if (Object.prototype.toString.call(className) !== '[object Array]') {
14498                 if (typeof className === 'string') {
14499                     className = className.replace(trimRe, '').split(spacesRe);
14500                     if (className.length === 1) {
14501                         className = className[0];
14502                         if (!me.hasCls(className)) {
14503                             me.dom.className += space + className;
14504                         }
14505                     } else {
14506                         this.addCls(className);
14507                     }
14508                 }
14509             } else {
14510                 for (i = 0, len = className.length; i < len; i++) {
14511                     v = className[i];
14512                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
14513                         cls.push(v);
14514                     }
14515                 }
14516                 if (cls.length) {
14517                     me.dom.className += space + cls.join(" ");
14518                 }
14519             }
14520             return me;
14521         },
14522
14523         /**
14524          * Removes one or more CSS classes from the element.
14525          * @param {String/String[]} className The CSS classes to remove separated by space, or an array of classes
14526          * @return {Ext.Element} this
14527          */
14528         removeCls : function(className){
14529             var me = this,
14530                 i, idx, len, cls, elClasses;
14531             if (className === undefined) {
14532                 return me;
14533             }
14534             if (Object.prototype.toString.call(className) !== '[object Array]') {
14535                 className = className.replace(trimRe, '').split(spacesRe);
14536             }
14537             if (me.dom && me.dom.className) {
14538                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
14539                 for (i = 0, len = className.length; i < len; i++) {
14540                     cls = className[i];
14541                     if (typeof cls == 'string') {
14542                         cls = cls.replace(trimRe, '');
14543                         idx = Ext.Array.indexOf(elClasses, cls);
14544                         if (idx != -1) {
14545                             Ext.Array.erase(elClasses, idx, 1);
14546                         }
14547                     }
14548                 }
14549                 me.dom.className = elClasses.join(" ");
14550             }
14551             return me;
14552         },
14553
14554         /**
14555          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
14556          * @param {String/String[]} className The CSS class to add, or an array of classes
14557          * @return {Ext.Element} this
14558          */
14559         radioCls : function(className){
14560             var cn = this.dom.parentNode.childNodes,
14561                 v, i, len;
14562             className = Ext.isArray(className) ? className : [className];
14563             for (i = 0, len = cn.length; i < len; i++) {
14564                 v = cn[i];
14565                 if (v && v.nodeType == 1) {
14566                     Ext.fly(v, '_internal').removeCls(className);
14567                 }
14568             }
14569             return this.addCls(className);
14570         },
14571
14572         /**
14573          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
14574          * @param {String} className The CSS class to toggle
14575          * @return {Ext.Element} this
14576          * @method
14577          */
14578         toggleCls : Ext.supports.ClassList ?
14579             function(className) {
14580                 this.dom.classList.toggle(Ext.String.trim(className));
14581                 return this;
14582             } :
14583             function(className) {
14584                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
14585             },
14586
14587         /**
14588          * Checks if the specified CSS class exists on this element's DOM node.
14589          * @param {String} className The CSS class to check for
14590          * @return {Boolean} True if the class exists, else false
14591          * @method
14592          */
14593         hasCls : Ext.supports.ClassList ?
14594             function(className) {
14595                 if (!className) {
14596                     return false;
14597                 }
14598                 className = className.split(spacesRe);
14599                 var ln = className.length,
14600                     i = 0;
14601                 for (; i < ln; i++) {
14602                     if (className[i] && this.dom.classList.contains(className[i])) {
14603                         return true;
14604                     }
14605                 }
14606                 return false;
14607             } :
14608             function(className){
14609                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
14610             },
14611
14612         /**
14613          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
14614          * @param {String} oldClassName The CSS class to replace
14615          * @param {String} newClassName The replacement CSS class
14616          * @return {Ext.Element} this
14617          */
14618         replaceCls : function(oldClassName, newClassName){
14619             return this.removeCls(oldClassName).addCls(newClassName);
14620         },
14621
14622         isStyle : function(style, val) {
14623             return this.getStyle(style) == val;
14624         },
14625
14626         /**
14627          * Normalizes currentStyle and computedStyle.
14628          * @param {String} property The style property whose value is returned.
14629          * @return {String} The current value of the style property for this element.
14630          * @method
14631          */
14632         getStyle : function() {
14633             return view && view.getComputedStyle ?
14634                 function(prop){
14635                     var el = this.dom,
14636                         v, cs, out, display, cleaner;
14637
14638                     if(el == document){
14639                         return null;
14640                     }
14641                     prop = ELEMENT.normalize(prop);
14642                     out = (v = el.style[prop]) ? v :
14643                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
14644
14645                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
14646                     // numbers larger.
14647                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
14648                         cleaner = ELEMENT.getRightMarginFixCleaner(el);
14649                         display = this.getStyle('display');
14650                         el.style.display = 'inline-block';
14651                         out = view.getComputedStyle(el, '').marginRight;
14652                         el.style.display = display;
14653                         cleaner();
14654                     }
14655
14656                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
14657                         out = 'transparent';
14658                     }
14659                     return out;
14660                 } :
14661                 function (prop) {
14662                     var el = this.dom,
14663                         m, cs;
14664
14665                     if (el == document) {
14666                         return null;
14667                     }
14668                     prop = ELEMENT.normalize(prop);
14669
14670                     do {
14671                         if (prop == 'opacity') {
14672                             if (el.style.filter.match) {
14673                                 m = el.style.filter.match(opacityRe);
14674                                 if(m){
14675                                     var fv = parseFloat(m[1]);
14676                                     if(!isNaN(fv)){
14677                                         return fv ? fv / 100 : 0;
14678                                     }
14679                                 }
14680                             }
14681                             return 1;
14682                         }
14683
14684                         // the try statement does have a cost, so we avoid it unless we are
14685                         // on IE6
14686                         if (!Ext.isIE6) {
14687                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14688                         }
14689
14690                         try {
14691                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14692                         } catch (e) {
14693                             // in some cases, IE6 will throw Invalid Argument for properties
14694                             // like fontSize (see in /examples/tabs/tabs.html).
14695                         }
14696
14697                         if (!ELEMENT.inheritedProps[prop]) {
14698                             break;
14699                         }
14700
14701                         el = el.parentNode;
14702                         // this is _not_ perfect, but we can only hope that the style we
14703                         // need is inherited from a parentNode. If not and since IE won't
14704                         // give us the info we need, we are never going to be 100% right.
14705                     } while (el);
14706
14707                     Ext.log({
14708                         level: 'warn',
14709                         msg: 'Failed to get ' + this.dom.id + '.currentStyle.' + prop
14710                     });
14711                     return null;
14712                 }
14713         }(),
14714
14715         /**
14716          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
14717          * are convert to standard 6 digit hex color.
14718          * @param {String} attr The css attribute
14719          * @param {String} defaultValue The default value to use when a valid color isn't found
14720          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
14721          * color anims.
14722          */
14723         getColor : function(attr, defaultValue, prefix){
14724             var v = this.getStyle(attr),
14725                 color = prefix || prefix === '' ? prefix : '#',
14726                 h;
14727
14728             if(!v || (/transparent|inherit/.test(v))) {
14729                 return defaultValue;
14730             }
14731             if(/^r/.test(v)){
14732                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
14733                     h = parseInt(s, 10);
14734                     color += (h < 16 ? '0' : '') + h.toString(16);
14735                 });
14736             }else{
14737                 v = v.replace('#', '');
14738                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
14739             }
14740             return(color.length > 5 ? color.toLowerCase() : defaultValue);
14741         },
14742
14743         /**
14744          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
14745          * @param {String/Object} property The style property to be set, or an object of multiple styles.
14746          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
14747          * @return {Ext.Element} this
14748          */
14749         setStyle : function(prop, value){
14750             var me = this,
14751                 tmp, style;
14752
14753             if (!me.dom) {
14754                 return me;
14755             }
14756             if (typeof prop === 'string') {
14757                 tmp = {};
14758                 tmp[prop] = value;
14759                 prop = tmp;
14760             }
14761             for (style in prop) {
14762                 if (prop.hasOwnProperty(style)) {
14763                     value = Ext.value(prop[style], '');
14764                     if (style == 'opacity') {
14765                         me.setOpacity(value);
14766                     }
14767                     else {
14768                         me.dom.style[ELEMENT.normalize(style)] = value;
14769                     }
14770                 }
14771             }
14772             return me;
14773         },
14774
14775         /**
14776          * Set the opacity of the element
14777          * @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
14778          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
14779          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
14780          * @return {Ext.Element} this
14781          */
14782         setOpacity: function(opacity, animate) {
14783             var me = this,
14784                 dom = me.dom,
14785                 val,
14786                 style;
14787
14788             if (!me.dom) {
14789                 return me;
14790             }
14791
14792             style = me.dom.style;
14793
14794             if (!animate || !me.anim) {
14795                 if (!Ext.supports.Opacity) {
14796                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
14797                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
14798
14799                     style.zoom = 1;
14800                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
14801                 }
14802                 else {
14803                     style.opacity = opacity;
14804                 }
14805             }
14806             else {
14807                 if (!Ext.isObject(animate)) {
14808                     animate = {
14809                         duration: 350,
14810                         easing: 'ease-in'
14811                     };
14812                 }
14813                 me.animate(Ext.applyIf({
14814                     to: {
14815                         opacity: opacity
14816                     }
14817                 },
14818                 animate));
14819             }
14820             return me;
14821         },
14822
14823
14824         /**
14825          * Clears any opacity settings from this element. Required in some cases for IE.
14826          * @return {Ext.Element} this
14827          */
14828         clearOpacity : function(){
14829             var style = this.dom.style;
14830             if(!Ext.supports.Opacity){
14831                 if(!Ext.isEmpty(style.filter)){
14832                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
14833                 }
14834             }else{
14835                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
14836             }
14837             return this;
14838         },
14839
14840         /**
14841          * @private
14842          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
14843          * @return {Number} 0 or 1
14844          */
14845         adjustDirect2DDimension: function(dimension) {
14846             var me = this,
14847                 dom = me.dom,
14848                 display = me.getStyle('display'),
14849                 inlineDisplay = dom.style['display'],
14850                 inlinePosition = dom.style['position'],
14851                 originIndex = dimension === 'width' ? 0 : 1,
14852                 floating;
14853
14854             if (display === 'inline') {
14855                 dom.style['display'] = 'inline-block';
14856             }
14857
14858             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
14859
14860             // floating will contain digits that appears after the decimal point
14861             // if height or width are set to auto we fallback to msTransformOrigin calculation
14862             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
14863
14864             dom.style['position'] = inlinePosition;
14865
14866             if (display === 'inline') {
14867                 dom.style['display'] = inlineDisplay;
14868             }
14869
14870             return floating;
14871         },
14872
14873         /**
14874          * Returns the offset height of the element
14875          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
14876          * @return {Number} The element's height
14877          */
14878         getHeight: function(contentHeight, preciseHeight) {
14879             var me = this,
14880                 dom = me.dom,
14881                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14882                 height, overflow, style, floating;
14883
14884             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14885             // We will put the overflow back to it's original value when we are done measuring.
14886             if (Ext.isIEQuirks) {
14887                 style = dom.style;
14888                 overflow = style.overflow;
14889                 me.setStyle({ overflow: 'hidden'});
14890             }
14891
14892             height = dom.offsetHeight;
14893
14894             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
14895
14896             // IE9 Direct2D dimension rounding bug
14897             if (!hidden && Ext.supports.Direct2DBug) {
14898                 floating = me.adjustDirect2DDimension('height');
14899                 if (preciseHeight) {
14900                     height += floating;
14901                 }
14902                 else if (floating > 0 && floating < 0.5) {
14903                     height++;
14904                 }
14905             }
14906
14907             if (contentHeight) {
14908                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14909             }
14910
14911             if (Ext.isIEQuirks) {
14912                 me.setStyle({ overflow: overflow});
14913             }
14914
14915             if (height < 0) {
14916                 height = 0;
14917             }
14918             return height;
14919         },
14920
14921         /**
14922          * Returns the offset width of the element
14923          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
14924          * @return {Number} The element's width
14925          */
14926         getWidth: function(contentWidth, preciseWidth) {
14927             var me = this,
14928                 dom = me.dom,
14929                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14930                 rect, width, overflow, style, floating, parentPosition;
14931
14932             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14933             // We will put the overflow back to it's original value when we are done measuring.
14934             if (Ext.isIEQuirks) {
14935                 style = dom.style;
14936                 overflow = style.overflow;
14937                 me.setStyle({overflow: 'hidden'});
14938             }
14939
14940             // Fix Opera 10.5x width calculation issues
14941             if (Ext.isOpera10_5) {
14942                 if (dom.parentNode.currentStyle.position === 'relative') {
14943                     parentPosition = dom.parentNode.style.position;
14944                     dom.parentNode.style.position = 'static';
14945                     width = dom.offsetWidth;
14946                     dom.parentNode.style.position = parentPosition;
14947                 }
14948                 width = Math.max(width || 0, dom.offsetWidth);
14949
14950             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
14951             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
14952             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
14953             // subpixel measurements so we can force them to always be rounded up. See
14954             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
14955             } else if (Ext.supports.BoundingClientRect) {
14956                 rect = dom.getBoundingClientRect();
14957                 width = rect.right - rect.left;
14958                 width = preciseWidth ? width : Math.ceil(width);
14959             } else {
14960                 width = dom.offsetWidth;
14961             }
14962
14963             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
14964
14965             // IE9 Direct2D dimension rounding bug
14966             if (!hidden && Ext.supports.Direct2DBug) {
14967                 floating = me.adjustDirect2DDimension('width');
14968                 if (preciseWidth) {
14969                     width += floating;
14970                 }
14971                 else if (floating > 0 && floating < 0.5) {
14972                     width++;
14973                 }
14974             }
14975
14976             if (contentWidth) {
14977                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14978             }
14979
14980             if (Ext.isIEQuirks) {
14981                 me.setStyle({ overflow: overflow});
14982             }
14983
14984             if (width < 0) {
14985                 width = 0;
14986             }
14987             return width;
14988         },
14989
14990         /**
14991          * Set the width of this Element.
14992          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
14993          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14994          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
14995          * </ul></div>
14996          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14997          * @return {Ext.Element} this
14998          */
14999         setWidth : function(width, animate){
15000             var me = this;
15001             width = me.adjustWidth(width);
15002             if (!animate || !me.anim) {
15003                 me.dom.style.width = me.addUnits(width);
15004             }
15005             else {
15006                 if (!Ext.isObject(animate)) {
15007                     animate = {};
15008                 }
15009                 me.animate(Ext.applyIf({
15010                     to: {
15011                         width: width
15012                     }
15013                 }, animate));
15014             }
15015             return me;
15016         },
15017
15018         /**
15019          * Set the height of this Element.
15020          * <pre><code>
15021 // change the height to 200px and animate with default configuration
15022 Ext.fly('elementId').setHeight(200, true);
15023
15024 // change the height to 150px and animate with a custom configuration
15025 Ext.fly('elId').setHeight(150, {
15026     duration : .5, // animation will have a duration of .5 seconds
15027     // will change the content to "finished"
15028     callback: function(){ this.{@link #update}("finished"); }
15029 });
15030          * </code></pre>
15031          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
15032          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
15033          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
15034          * </ul></div>
15035          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15036          * @return {Ext.Element} this
15037          */
15038          setHeight : function(height, animate){
15039             var me = this;
15040             height = me.adjustHeight(height);
15041             if (!animate || !me.anim) {
15042                 me.dom.style.height = me.addUnits(height);
15043             }
15044             else {
15045                 if (!Ext.isObject(animate)) {
15046                     animate = {};
15047                 }
15048                 me.animate(Ext.applyIf({
15049                     to: {
15050                         height: height
15051                     }
15052                 }, animate));
15053             }
15054             return me;
15055         },
15056
15057         /**
15058          * Gets the width of the border(s) for the specified side(s)
15059          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
15060          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
15061          * @return {Number} The width of the sides passed added together
15062          */
15063         getBorderWidth : function(side){
15064             return this.addStyles(side, borders);
15065         },
15066
15067         /**
15068          * Gets the width of the padding(s) for the specified side(s)
15069          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
15070          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
15071          * @return {Number} The padding of the sides passed added together
15072          */
15073         getPadding : function(side){
15074             return this.addStyles(side, paddings);
15075         },
15076
15077         /**
15078          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
15079          * @return {Ext.Element} this
15080          */
15081         clip : function(){
15082             var me = this,
15083                 dom = me.dom;
15084
15085             if(!data(dom, ISCLIPPED)){
15086                 data(dom, ISCLIPPED, true);
15087                 data(dom, ORIGINALCLIP, {
15088                     o: me.getStyle(OVERFLOW),
15089                     x: me.getStyle(OVERFLOWX),
15090                     y: me.getStyle(OVERFLOWY)
15091                 });
15092                 me.setStyle(OVERFLOW, HIDDEN);
15093                 me.setStyle(OVERFLOWX, HIDDEN);
15094                 me.setStyle(OVERFLOWY, HIDDEN);
15095             }
15096             return me;
15097         },
15098
15099         /**
15100          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
15101          * @return {Ext.Element} this
15102          */
15103         unclip : function(){
15104             var me = this,
15105                 dom = me.dom,
15106                 clip;
15107
15108             if(data(dom, ISCLIPPED)){
15109                 data(dom, ISCLIPPED, false);
15110                 clip = data(dom, ORIGINALCLIP);
15111                 if(clip.o){
15112                     me.setStyle(OVERFLOW, clip.o);
15113                 }
15114                 if(clip.x){
15115                     me.setStyle(OVERFLOWX, clip.x);
15116                 }
15117                 if(clip.y){
15118                     me.setStyle(OVERFLOWY, clip.y);
15119                 }
15120             }
15121             return me;
15122         },
15123
15124         // private
15125         addStyles : function(sides, styles){
15126             var totalSize = 0,
15127                 sidesArr = sides.match(wordsRe),
15128                 i = 0,
15129                 len = sidesArr.length,
15130                 side, size;
15131             for (; i < len; i++) {
15132                 side = sidesArr[i];
15133                 size = side && parseInt(this.getStyle(styles[side]), 10);
15134                 if (size) {
15135                     totalSize += MATH.abs(size);
15136                 }
15137             }
15138             return totalSize;
15139         },
15140
15141         margins : margins,
15142
15143         /**
15144          * More flexible version of {@link #setStyle} for setting style properties.
15145          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
15146          * a function which returns such a specification.
15147          * @return {Ext.Element} this
15148          */
15149         applyStyles : function(style){
15150             Ext.DomHelper.applyStyles(this.dom, style);
15151             return this;
15152         },
15153
15154         /**
15155          * Returns an object with properties matching the styles requested.
15156          * For example, el.getStyles('color', 'font-size', 'width') might return
15157          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
15158          * @param {String} style1 A style name
15159          * @param {String} style2 A style name
15160          * @param {String} etc.
15161          * @return {Object} The style object
15162          */
15163         getStyles : function(){
15164             var styles = {},
15165                 len = arguments.length,
15166                 i = 0, style;
15167
15168             for(; i < len; ++i) {
15169                 style = arguments[i];
15170                 styles[style] = this.getStyle(style);
15171             }
15172             return styles;
15173         },
15174
15175        /**
15176         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
15177         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
15178         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
15179         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
15180         * is of this form:</p>
15181         * <pre><code>
15182     Ext.Element.boxMarkup =
15183     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
15184      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
15185      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
15186         * </code></pre>
15187         * <p>Example usage:</p>
15188         * <pre><code>
15189     // Basic box wrap
15190     Ext.get("foo").boxWrap();
15191
15192     // You can also add a custom class and use CSS inheritance rules to customize the box look.
15193     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
15194     // for how to create a custom box wrap style.
15195     Ext.get("foo").boxWrap().addCls("x-box-blue");
15196         * </code></pre>
15197         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
15198         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
15199         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
15200         * also supply all of the necessary rules.
15201         * @return {Ext.Element} The outermost wrapping element of the created box structure.
15202         */
15203         boxWrap : function(cls){
15204             cls = cls || Ext.baseCSSPrefix + 'box';
15205             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(ELEMENT.boxMarkup, cls) + "</div>"));
15206             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
15207             return el;
15208         },
15209
15210         /**
15211          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
15212          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
15213          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
15214          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
15215          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
15216          * </ul></div>
15217          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
15218          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
15219          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
15220          * </ul></div>
15221          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15222          * @return {Ext.Element} this
15223          */
15224         setSize : function(width, height, animate){
15225             var me = this;
15226             if (Ext.isObject(width)) { // in case of object from getSize()
15227                 animate = height;
15228                 height = width.height;
15229                 width = width.width;
15230             }
15231             width = me.adjustWidth(width);
15232             height = me.adjustHeight(height);
15233             if(!animate || !me.anim){
15234                 // Must touch some property before setting style.width/height on non-quirk IE6,7, or the
15235                 // properties will not reflect the changes on the style immediately
15236                 if (!Ext.isIEQuirks && (Ext.isIE6 || Ext.isIE7)) {
15237                     me.dom.offsetTop;
15238                 }
15239                 me.dom.style.width = me.addUnits(width);
15240                 me.dom.style.height = me.addUnits(height);
15241             }
15242             else {
15243                 if (animate === true) {
15244                     animate = {};
15245                 }
15246                 me.animate(Ext.applyIf({
15247                     to: {
15248                         width: width,
15249                         height: height
15250                     }
15251                 }, animate));
15252             }
15253             return me;
15254         },
15255
15256         /**
15257          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
15258          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
15259          * if a height has not been set using CSS.
15260          * @return {Number}
15261          */
15262         getComputedHeight : function(){
15263             var me = this,
15264                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
15265             if(!h){
15266                 h = parseFloat(me.getStyle('height')) || 0;
15267                 if(!me.isBorderBox()){
15268                     h += me.getFrameWidth('tb');
15269                 }
15270             }
15271             return h;
15272         },
15273
15274         /**
15275          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
15276          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
15277          * if a width has not been set using CSS.
15278          * @return {Number}
15279          */
15280         getComputedWidth : function(){
15281             var me = this,
15282                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
15283
15284             if(!w){
15285                 w = parseFloat(me.getStyle('width')) || 0;
15286                 if(!me.isBorderBox()){
15287                     w += me.getFrameWidth('lr');
15288                 }
15289             }
15290             return w;
15291         },
15292
15293         /**
15294          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
15295          for more information about the sides.
15296          * @param {String} sides
15297          * @return {Number}
15298          */
15299         getFrameWidth : function(sides, onlyContentBox){
15300             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
15301         },
15302
15303         /**
15304          * Sets up event handlers to add and remove a css class when the mouse is over this element
15305          * @param {String} className
15306          * @return {Ext.Element} this
15307          */
15308         addClsOnOver : function(className){
15309             var dom = this.dom;
15310             this.hover(
15311                 function(){
15312                     Ext.fly(dom, INTERNAL).addCls(className);
15313                 },
15314                 function(){
15315                     Ext.fly(dom, INTERNAL).removeCls(className);
15316                 }
15317             );
15318             return this;
15319         },
15320
15321         /**
15322          * Sets up event handlers to add and remove a css class when this element has the focus
15323          * @param {String} className
15324          * @return {Ext.Element} this
15325          */
15326         addClsOnFocus : function(className){
15327             var me = this,
15328                 dom = me.dom;
15329             me.on("focus", function(){
15330                 Ext.fly(dom, INTERNAL).addCls(className);
15331             });
15332             me.on("blur", function(){
15333                 Ext.fly(dom, INTERNAL).removeCls(className);
15334             });
15335             return me;
15336         },
15337
15338         /**
15339          * 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)
15340          * @param {String} className
15341          * @return {Ext.Element} this
15342          */
15343         addClsOnClick : function(className){
15344             var dom = this.dom;
15345             this.on("mousedown", function(){
15346                 Ext.fly(dom, INTERNAL).addCls(className);
15347                 var d = Ext.getDoc(),
15348                     fn = function(){
15349                         Ext.fly(dom, INTERNAL).removeCls(className);
15350                         d.removeListener("mouseup", fn);
15351                     };
15352                 d.on("mouseup", fn);
15353             });
15354             return this;
15355         },
15356
15357         /**
15358          * <p>Returns the dimensions of the element available to lay content out in.<p>
15359          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
15360          * example:<pre><code>
15361         var vpSize = Ext.getBody().getViewSize();
15362
15363         // all Windows created afterwards will have a default value of 90% height and 95% width
15364         Ext.Window.override({
15365             width: vpSize.width * 0.9,
15366             height: vpSize.height * 0.95
15367         });
15368         // To handle window resizing you would have to hook onto onWindowResize.
15369         * </code></pre>
15370         *
15371         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
15372         * To obtain the size including scrollbars, use getStyleSize
15373         *
15374         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
15375         */
15376
15377         getViewSize : function(){
15378             var me = this,
15379                 dom = me.dom,
15380                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
15381                 style, overflow, ret;
15382
15383             // If the body, use static methods
15384             if (isDoc) {
15385                 ret = {
15386                     width : ELEMENT.getViewWidth(),
15387                     height : ELEMENT.getViewHeight()
15388                 };
15389
15390             // Else use clientHeight/clientWidth
15391             }
15392             else {
15393                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
15394                 // We will put the overflow back to it's original value when we are done measuring.
15395                 if (Ext.isIE6 || Ext.isIEQuirks) {
15396                     style = dom.style;
15397                     overflow = style.overflow;
15398                     me.setStyle({ overflow: 'hidden'});
15399                 }
15400                 ret = {
15401                     width : dom.clientWidth,
15402                     height : dom.clientHeight
15403                 };
15404                 if (Ext.isIE6 || Ext.isIEQuirks) {
15405                     me.setStyle({ overflow: overflow });
15406                 }
15407             }
15408             return ret;
15409         },
15410
15411         /**
15412         * <p>Returns the dimensions of the element available to lay content out in.<p>
15413         *
15414         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
15415         * To obtain the size excluding scrollbars, use getViewSize
15416         *
15417         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
15418         */
15419
15420         getStyleSize : function(){
15421             var me = this,
15422                 doc = document,
15423                 d = this.dom,
15424                 isDoc = (d == doc || d == doc.body),
15425                 s = d.style,
15426                 w, h;
15427
15428             // If the body, use static methods
15429             if (isDoc) {
15430                 return {
15431                     width : ELEMENT.getViewWidth(),
15432                     height : ELEMENT.getViewHeight()
15433                 };
15434             }
15435             // Use Styles if they are set
15436             if(s.width && s.width != 'auto'){
15437                 w = parseFloat(s.width);
15438                 if(me.isBorderBox()){
15439                    w -= me.getFrameWidth('lr');
15440                 }
15441             }
15442             // Use Styles if they are set
15443             if(s.height && s.height != 'auto'){
15444                 h = parseFloat(s.height);
15445                 if(me.isBorderBox()){
15446                    h -= me.getFrameWidth('tb');
15447                 }
15448             }
15449             // Use getWidth/getHeight if style not set.
15450             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
15451         },
15452
15453         /**
15454          * Returns the size of the element.
15455          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
15456          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
15457          */
15458         getSize : function(contentSize){
15459             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
15460         },
15461
15462         /**
15463          * Forces the browser to repaint this element
15464          * @return {Ext.Element} this
15465          */
15466         repaint : function(){
15467             var dom = this.dom;
15468             this.addCls(Ext.baseCSSPrefix + 'repaint');
15469             setTimeout(function(){
15470                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
15471             }, 1);
15472             return this;
15473         },
15474
15475         /**
15476          * Enable text selection for this element (normalized across browsers)
15477          * @return {Ext.Element} this
15478          */
15479         selectable : function() {
15480             var me = this;
15481             me.dom.unselectable = "off";
15482             // Prevent it from bubles up and enables it to be selectable
15483             me.on('selectstart', function (e) {
15484                 e.stopPropagation();
15485                 return true;
15486             });
15487             me.applyStyles("-moz-user-select: text; -khtml-user-select: text;");
15488             me.removeCls(Ext.baseCSSPrefix + 'unselectable');
15489             return me;
15490         },
15491
15492         /**
15493          * Disables text selection for this element (normalized across browsers)
15494          * @return {Ext.Element} this
15495          */
15496         unselectable : function(){
15497             var me = this;
15498             me.dom.unselectable = "on";
15499
15500             me.swallowEvent("selectstart", true);
15501             me.applyStyles("-moz-user-select:-moz-none;-khtml-user-select:none;");
15502             me.addCls(Ext.baseCSSPrefix + 'unselectable');
15503
15504             return me;
15505         },
15506
15507         /**
15508          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
15509          * then it returns the calculated width of the sides (see getPadding)
15510          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
15511          * @return {Object/Number}
15512          */
15513         getMargin : function(side){
15514             var me = this,
15515                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
15516                 o = {},
15517                 key;
15518
15519             if (!side) {
15520                 for (key in me.margins){
15521                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
15522                 }
15523                 return o;
15524             } else {
15525                 return me.addStyles.call(me, side, me.margins);
15526             }
15527         }
15528     });
15529 })();
15530 /**
15531  * @class Ext.Element
15532  */
15533 /**
15534  * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
15535  * @static
15536  * @type Number
15537  */
15538 Ext.Element.VISIBILITY = 1;
15539 /**
15540  * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
15541  * @static
15542  * @type Number
15543  */
15544 Ext.Element.DISPLAY = 2;
15545
15546 /**
15547  * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
15548  * to hide element.
15549  * @static
15550  * @type Number
15551  */
15552 Ext.Element.OFFSETS = 3;
15553
15554
15555 Ext.Element.ASCLASS = 4;
15556
15557 /**
15558  * Defaults to 'x-hide-nosize'
15559  * @static
15560  * @type String
15561  */
15562 Ext.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
15563
15564 Ext.Element.addMethods(function(){
15565     var El = Ext.Element,
15566         OPACITY = "opacity",
15567         VISIBILITY = "visibility",
15568         DISPLAY = "display",
15569         HIDDEN = "hidden",
15570         OFFSETS = "offsets",
15571         ASCLASS = "asclass",
15572         NONE = "none",
15573         NOSIZE = 'nosize',
15574         ORIGINALDISPLAY = 'originalDisplay',
15575         VISMODE = 'visibilityMode',
15576         ISVISIBLE = 'isVisible',
15577         data = El.data,
15578         getDisplay = function(dom){
15579             var d = data(dom, ORIGINALDISPLAY);
15580             if(d === undefined){
15581                 data(dom, ORIGINALDISPLAY, d = '');
15582             }
15583             return d;
15584         },
15585         getVisMode = function(dom){
15586             var m = data(dom, VISMODE);
15587             if(m === undefined){
15588                 data(dom, VISMODE, m = 1);
15589             }
15590             return m;
15591         };
15592
15593     return {
15594         /**
15595          * @property {String} originalDisplay
15596          * The element's default display mode
15597          */
15598         originalDisplay : "",
15599         visibilityMode : 1,
15600
15601         /**
15602          * Sets the element's visibility mode. When setVisible() is called it
15603          * will use this to determine whether to set the visibility or the display property.
15604          * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
15605          * @return {Ext.Element} this
15606          */
15607         setVisibilityMode : function(visMode){
15608             data(this.dom, VISMODE, visMode);
15609             return this;
15610         },
15611
15612         /**
15613          * Checks whether the element is currently visible using both visibility and display properties.
15614          * @return {Boolean} True if the element is currently visible, else false
15615          */
15616         isVisible : function() {
15617             var me = this,
15618                 dom = me.dom,
15619                 visible = data(dom, ISVISIBLE);
15620
15621             if(typeof visible == 'boolean'){ //return the cached value if registered
15622                 return visible;
15623             }
15624             //Determine the current state based on display states
15625             visible = !me.isStyle(VISIBILITY, HIDDEN) &&
15626                       !me.isStyle(DISPLAY, NONE) &&
15627                       !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
15628
15629             data(dom, ISVISIBLE, visible);
15630             return visible;
15631         },
15632
15633         /**
15634          * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
15635          * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
15636          * @param {Boolean} visible Whether the element is visible
15637          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15638          * @return {Ext.Element} this
15639          */
15640         setVisible : function(visible, animate){
15641             var me = this, isDisplay, isVisibility, isOffsets, isNosize,
15642                 dom = me.dom,
15643                 visMode = getVisMode(dom);
15644
15645
15646             // hideMode string override
15647             if (typeof animate == 'string'){
15648                 switch (animate) {
15649                     case DISPLAY:
15650                         visMode = El.DISPLAY;
15651                         break;
15652                     case VISIBILITY:
15653                         visMode = El.VISIBILITY;
15654                         break;
15655                     case OFFSETS:
15656                         visMode = El.OFFSETS;
15657                         break;
15658                     case NOSIZE:
15659                     case ASCLASS:
15660                         visMode = El.ASCLASS;
15661                         break;
15662                 }
15663                 me.setVisibilityMode(visMode);
15664                 animate = false;
15665             }
15666
15667             if (!animate || !me.anim) {
15668                 if(visMode == El.ASCLASS ){
15669
15670                     me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
15671
15672                 } else if (visMode == El.DISPLAY){
15673
15674                     return me.setDisplayed(visible);
15675
15676                 } else if (visMode == El.OFFSETS){
15677
15678                     if (!visible){
15679                         // Remember position for restoring, if we are not already hidden by offsets.
15680                         if (!me.hideModeStyles) {
15681                             me.hideModeStyles = {
15682                                 position: me.getStyle('position'),
15683                                 top: me.getStyle('top'),
15684                                 left: me.getStyle('left')
15685                             };
15686                         }
15687                         me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
15688                     }
15689
15690                     // Only "restore" as position if we have actually been hidden using offsets.
15691                     // Calling setVisible(true) on a positioned element should not reposition it.
15692                     else if (me.hideModeStyles) {
15693                         me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
15694                         delete me.hideModeStyles;
15695                     }
15696
15697                 }else{
15698                     me.fixDisplay();
15699                     // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting.
15700                     dom.style.visibility = visible ? '' : HIDDEN;
15701                 }
15702             }else{
15703                 // closure for composites
15704                 if(visible){
15705                     me.setOpacity(0.01);
15706                     me.setVisible(true);
15707                 }
15708                 if (!Ext.isObject(animate)) {
15709                     animate = {
15710                         duration: 350,
15711                         easing: 'ease-in'
15712                     };
15713                 }
15714                 me.animate(Ext.applyIf({
15715                     callback: function() {
15716                         visible || me.setVisible(false).setOpacity(1);
15717                     },
15718                     to: {
15719                         opacity: (visible) ? 1 : 0
15720                     }
15721                 }, animate));
15722             }
15723             data(dom, ISVISIBLE, visible);  //set logical visibility state
15724             return me;
15725         },
15726
15727
15728         /**
15729          * @private
15730          * Determine if the Element has a relevant height and width available based
15731          * upon current logical visibility state
15732          */
15733         hasMetrics  : function(){
15734             var dom = this.dom;
15735             return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
15736         },
15737
15738         /**
15739          * Toggles the element's visibility or display, depending on visibility mode.
15740          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15741          * @return {Ext.Element} this
15742          */
15743         toggle : function(animate){
15744             var me = this;
15745             me.setVisible(!me.isVisible(), me.anim(animate));
15746             return me;
15747         },
15748
15749         /**
15750          * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
15751          * @param {Boolean/String} value Boolean value to display the element using its default display, or a string to set the display directly.
15752          * @return {Ext.Element} this
15753          */
15754         setDisplayed : function(value) {
15755             if(typeof value == "boolean"){
15756                value = value ? getDisplay(this.dom) : NONE;
15757             }
15758             this.setStyle(DISPLAY, value);
15759             return this;
15760         },
15761
15762         // private
15763         fixDisplay : function(){
15764             var me = this;
15765             if (me.isStyle(DISPLAY, NONE)) {
15766                 me.setStyle(VISIBILITY, HIDDEN);
15767                 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
15768                 if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
15769                     me.setStyle(DISPLAY, "block");
15770                 }
15771             }
15772         },
15773
15774         /**
15775          * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15776          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15777          * @return {Ext.Element} this
15778          */
15779         hide : function(animate){
15780             // hideMode override
15781             if (typeof animate == 'string'){
15782                 this.setVisible(false, animate);
15783                 return this;
15784             }
15785             this.setVisible(false, this.anim(animate));
15786             return this;
15787         },
15788
15789         /**
15790         * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15791         * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15792          * @return {Ext.Element} this
15793          */
15794         show : function(animate){
15795             // hideMode override
15796             if (typeof animate == 'string'){
15797                 this.setVisible(true, animate);
15798                 return this;
15799             }
15800             this.setVisible(true, this.anim(animate));
15801             return this;
15802         }
15803     };
15804 }());
15805 /**
15806  * @class Ext.Element
15807  */
15808 Ext.applyIf(Ext.Element.prototype, {
15809     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15810     animate: function(config) {
15811         var me = this;
15812         if (!me.id) {
15813             me = Ext.get(me.dom);
15814         }
15815         if (Ext.fx.Manager.hasFxBlock(me.id)) {
15816             return me;
15817         }
15818         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
15819         return this;
15820     },
15821
15822     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15823     anim: function(config) {
15824         if (!Ext.isObject(config)) {
15825             return (config) ? {} : false;
15826         }
15827
15828         var me = this,
15829             duration = config.duration || Ext.fx.Anim.prototype.duration,
15830             easing = config.easing || 'ease',
15831             animConfig;
15832
15833         if (config.stopAnimation) {
15834             me.stopAnimation();
15835         }
15836
15837         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
15838
15839         // Clear any 'paused' defaults.
15840         Ext.fx.Manager.setFxDefaults(me.id, {
15841             delay: 0
15842         });
15843
15844         animConfig = {
15845             target: me,
15846             remove: config.remove,
15847             alternate: config.alternate || false,
15848             duration: duration,
15849             easing: easing,
15850             callback: config.callback,
15851             listeners: config.listeners,
15852             iterations: config.iterations || 1,
15853             scope: config.scope,
15854             block: config.block,
15855             concurrent: config.concurrent,
15856             delay: config.delay || 0,
15857             paused: true,
15858             keyframes: config.keyframes,
15859             from: config.from || {},
15860             to: Ext.apply({}, config)
15861         };
15862         Ext.apply(animConfig.to, config.to);
15863
15864         // Anim API properties - backward compat
15865         delete animConfig.to.to;
15866         delete animConfig.to.from;
15867         delete animConfig.to.remove;
15868         delete animConfig.to.alternate;
15869         delete animConfig.to.keyframes;
15870         delete animConfig.to.iterations;
15871         delete animConfig.to.listeners;
15872         delete animConfig.to.target;
15873         delete animConfig.to.paused;
15874         delete animConfig.to.callback;
15875         delete animConfig.to.scope;
15876         delete animConfig.to.duration;
15877         delete animConfig.to.easing;
15878         delete animConfig.to.concurrent;
15879         delete animConfig.to.block;
15880         delete animConfig.to.stopAnimation;
15881         delete animConfig.to.delay;
15882         return animConfig;
15883     },
15884
15885     /**
15886      * Slides the element into view. An anchor point can be optionally passed to set the point of origin for the slide
15887      * effect. This function automatically handles wrapping the element with a fixed-size container if needed. See the
15888      * Fx class overview for valid anchor point options. Usage:
15889      *
15890      *     // default: slide the element in from the top
15891      *     el.slideIn();
15892      *
15893      *     // custom: slide the element in from the right with a 2-second duration
15894      *     el.slideIn('r', { duration: 2000 });
15895      *
15896      *     // common config options shown with default values
15897      *     el.slideIn('t', {
15898      *         easing: 'easeOut',
15899      *         duration: 500
15900      *     });
15901      *
15902      * @param {String} [anchor='t'] One of the valid Fx anchor positions
15903      * @param {Object} [options] Object literal with any of the Fx config options
15904      * @return {Ext.Element} The Element
15905      */
15906     slideIn: function(anchor, obj, slideOut) {
15907         var me = this,
15908             elStyle = me.dom.style,
15909             beforeAnim, wrapAnim;
15910
15911         anchor = anchor || "t";
15912         obj = obj || {};
15913
15914         beforeAnim = function() {
15915             var animScope = this,
15916                 listeners = obj.listeners,
15917                 box, position, restoreSize, wrap, anim;
15918
15919             if (!slideOut) {
15920                 me.fixDisplay();
15921             }
15922
15923             box = me.getBox();
15924             if ((anchor == 't' || anchor == 'b') && box.height === 0) {
15925                 box.height = me.dom.scrollHeight;
15926             }
15927             else if ((anchor == 'l' || anchor == 'r') && box.width === 0) {
15928                 box.width = me.dom.scrollWidth;
15929             }
15930
15931             position = me.getPositioning();
15932             me.setSize(box.width, box.height);
15933
15934             wrap = me.wrap({
15935                 style: {
15936                     visibility: slideOut ? 'visible' : 'hidden'
15937                 }
15938             });
15939             wrap.setPositioning(position);
15940             if (wrap.isStyle('position', 'static')) {
15941                 wrap.position('relative');
15942             }
15943             me.clearPositioning('auto');
15944             wrap.clip();
15945
15946             // This element is temporarily positioned absolute within its wrapper.
15947             // Restore to its default, CSS-inherited visibility setting.
15948             // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
15949             me.setStyle({
15950                 visibility: '',
15951                 position: 'absolute'
15952             });
15953             if (slideOut) {
15954                 wrap.setSize(box.width, box.height);
15955             }
15956
15957             switch (anchor) {
15958                 case 't':
15959                     anim = {
15960                         from: {
15961                             width: box.width + 'px',
15962                             height: '0px'
15963                         },
15964                         to: {
15965                             width: box.width + 'px',
15966                             height: box.height + 'px'
15967                         }
15968                     };
15969                     elStyle.bottom = '0px';
15970                     break;
15971                 case 'l':
15972                     anim = {
15973                         from: {
15974                             width: '0px',
15975                             height: box.height + 'px'
15976                         },
15977                         to: {
15978                             width: box.width + 'px',
15979                             height: box.height + 'px'
15980                         }
15981                     };
15982                     elStyle.right = '0px';
15983                     break;
15984                 case 'r':
15985                     anim = {
15986                         from: {
15987                             x: box.x + box.width,
15988                             width: '0px',
15989                             height: box.height + 'px'
15990                         },
15991                         to: {
15992                             x: box.x,
15993                             width: box.width + 'px',
15994                             height: box.height + 'px'
15995                         }
15996                     };
15997                     break;
15998                 case 'b':
15999                     anim = {
16000                         from: {
16001                             y: box.y + box.height,
16002                             width: box.width + 'px',
16003                             height: '0px'
16004                         },
16005                         to: {
16006                             y: box.y,
16007                             width: box.width + 'px',
16008                             height: box.height + 'px'
16009                         }
16010                     };
16011                     break;
16012                 case 'tl':
16013                     anim = {
16014                         from: {
16015                             x: box.x,
16016                             y: box.y,
16017                             width: '0px',
16018                             height: '0px'
16019                         },
16020                         to: {
16021                             width: box.width + 'px',
16022                             height: box.height + 'px'
16023                         }
16024                     };
16025                     elStyle.bottom = '0px';
16026                     elStyle.right = '0px';
16027                     break;
16028                 case 'bl':
16029                     anim = {
16030                         from: {
16031                             x: box.x + box.width,
16032                             width: '0px',
16033                             height: '0px'
16034                         },
16035                         to: {
16036                             x: box.x,
16037                             width: box.width + 'px',
16038                             height: box.height + 'px'
16039                         }
16040                     };
16041                     elStyle.right = '0px';
16042                     break;
16043                 case 'br':
16044                     anim = {
16045                         from: {
16046                             x: box.x + box.width,
16047                             y: box.y + box.height,
16048                             width: '0px',
16049                             height: '0px'
16050                         },
16051                         to: {
16052                             x: box.x,
16053                             y: box.y,
16054                             width: box.width + 'px',
16055                             height: box.height + 'px'
16056                         }
16057                     };
16058                     break;
16059                 case 'tr':
16060                     anim = {
16061                         from: {
16062                             y: box.y + box.height,
16063                             width: '0px',
16064                             height: '0px'
16065                         },
16066                         to: {
16067                             y: box.y,
16068                             width: box.width + 'px',
16069                             height: box.height + 'px'
16070                         }
16071                     };
16072                     elStyle.bottom = '0px';
16073                     break;
16074             }
16075
16076             wrap.show();
16077             wrapAnim = Ext.apply({}, obj);
16078             delete wrapAnim.listeners;
16079             wrapAnim = Ext.create('Ext.fx.Anim', Ext.applyIf(wrapAnim, {
16080                 target: wrap,
16081                 duration: 500,
16082                 easing: 'ease-out',
16083                 from: slideOut ? anim.to : anim.from,
16084                 to: slideOut ? anim.from : anim.to
16085             }));
16086
16087             // In the absence of a callback, this listener MUST be added first
16088             wrapAnim.on('afteranimate', function() {
16089                 if (slideOut) {
16090                     me.setPositioning(position);
16091                     if (obj.useDisplay) {
16092                         me.setDisplayed(false);
16093                     } else {
16094                         me.hide();
16095                     }
16096                 }
16097                 else {
16098                     me.clearPositioning();
16099                     me.setPositioning(position);
16100                 }
16101                 if (wrap.dom) {
16102                     wrap.dom.parentNode.insertBefore(me.dom, wrap.dom);
16103                     wrap.remove();
16104                 }
16105                 me.setSize(box.width, box.height);
16106                 animScope.end();
16107             });
16108             // Add configured listeners after
16109             if (listeners) {
16110                 wrapAnim.on(listeners);
16111             }
16112         };
16113
16114         me.animate({
16115             duration: obj.duration ? obj.duration * 2 : 1000,
16116             listeners: {
16117                 beforeanimate: {
16118                     fn: beforeAnim
16119                 },
16120                 afteranimate: {
16121                     fn: function() {
16122                         if (wrapAnim && wrapAnim.running) {
16123                             wrapAnim.end();
16124                         }
16125                     }
16126                 }
16127             }
16128         });
16129         return me;
16130     },
16131
16132
16133     /**
16134      * Slides the element out of view. An anchor point can be optionally passed to set the end point for the slide
16135      * effect. When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will
16136      * still take up space in the document. The element must be removed from the DOM using the 'remove' config option if
16137      * desired. This function automatically handles wrapping the element with a fixed-size container if needed. See the
16138      * Fx class overview for valid anchor point options. Usage:
16139      *
16140      *     // default: slide the element out to the top
16141      *     el.slideOut();
16142      *
16143      *     // custom: slide the element out to the right with a 2-second duration
16144      *     el.slideOut('r', { duration: 2000 });
16145      *
16146      *     // common config options shown with default values
16147      *     el.slideOut('t', {
16148      *         easing: 'easeOut',
16149      *         duration: 500,
16150      *         remove: false,
16151      *         useDisplay: false
16152      *     });
16153      *
16154      * @param {String} [anchor='t'] One of the valid Fx anchor positions
16155      * @param {Object} [options] Object literal with any of the Fx config options
16156      * @return {Ext.Element} The Element
16157      */
16158     slideOut: function(anchor, o) {
16159         return this.slideIn(anchor, o, true);
16160     },
16161
16162     /**
16163      * Fades the element out while slowly expanding it in all directions. When the effect is completed, the element will
16164      * be hidden (visibility = 'hidden') but block elements will still take up space in the document. Usage:
16165      *
16166      *     // default
16167      *     el.puff();
16168      *
16169      *     // common config options shown with default values
16170      *     el.puff({
16171      *         easing: 'easeOut',
16172      *         duration: 500,
16173      *         useDisplay: false
16174      *     });
16175      *
16176      * @param {Object} options (optional) Object literal with any of the Fx config options
16177      * @return {Ext.Element} The Element
16178      */
16179     puff: function(obj) {
16180         var me = this,
16181             beforeAnim;
16182         obj = Ext.applyIf(obj || {}, {
16183             easing: 'ease-out',
16184             duration: 500,
16185             useDisplay: false
16186         });
16187
16188         beforeAnim = function() {
16189             me.clearOpacity();
16190             me.show();
16191
16192             var box = me.getBox(),
16193                 fontSize = me.getStyle('fontSize'),
16194                 position = me.getPositioning();
16195             this.to = {
16196                 width: box.width * 2,
16197                 height: box.height * 2,
16198                 x: box.x - (box.width / 2),
16199                 y: box.y - (box.height /2),
16200                 opacity: 0,
16201                 fontSize: '200%'
16202             };
16203             this.on('afteranimate',function() {
16204                 if (me.dom) {
16205                     if (obj.useDisplay) {
16206                         me.setDisplayed(false);
16207                     } else {
16208                         me.hide();
16209                     }
16210                     me.clearOpacity();
16211                     me.setPositioning(position);
16212                     me.setStyle({fontSize: fontSize});
16213                 }
16214             });
16215         };
16216
16217         me.animate({
16218             duration: obj.duration,
16219             easing: obj.easing,
16220             listeners: {
16221                 beforeanimate: {
16222                     fn: beforeAnim
16223                 }
16224             }
16225         });
16226         return me;
16227     },
16228
16229     /**
16230      * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
16231      * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
16232      * take up space in the document. The element must be removed from the DOM using the 'remove' config option if
16233      * desired. Usage:
16234      *
16235      *     // default
16236      *     el.switchOff();
16237      *
16238      *     // all config options shown with default values
16239      *     el.switchOff({
16240      *         easing: 'easeIn',
16241      *         duration: .3,
16242      *         remove: false,
16243      *         useDisplay: false
16244      *     });
16245      *
16246      * @param {Object} options (optional) Object literal with any of the Fx config options
16247      * @return {Ext.Element} The Element
16248      */
16249     switchOff: function(obj) {
16250         var me = this,
16251             beforeAnim;
16252
16253         obj = Ext.applyIf(obj || {}, {
16254             easing: 'ease-in',
16255             duration: 500,
16256             remove: false,
16257             useDisplay: false
16258         });
16259
16260         beforeAnim = function() {
16261             var animScope = this,
16262                 size = me.getSize(),
16263                 xy = me.getXY(),
16264                 keyframe, position;
16265             me.clearOpacity();
16266             me.clip();
16267             position = me.getPositioning();
16268
16269             keyframe = Ext.create('Ext.fx.Animator', {
16270                 target: me,
16271                 duration: obj.duration,
16272                 easing: obj.easing,
16273                 keyframes: {
16274                     33: {
16275                         opacity: 0.3
16276                     },
16277                     66: {
16278                         height: 1,
16279                         y: xy[1] + size.height / 2
16280                     },
16281                     100: {
16282                         width: 1,
16283                         x: xy[0] + size.width / 2
16284                     }
16285                 }
16286             });
16287             keyframe.on('afteranimate', function() {
16288                 if (obj.useDisplay) {
16289                     me.setDisplayed(false);
16290                 } else {
16291                     me.hide();
16292                 }
16293                 me.clearOpacity();
16294                 me.setPositioning(position);
16295                 me.setSize(size);
16296                 animScope.end();
16297             });
16298         };
16299         me.animate({
16300             duration: (obj.duration * 2),
16301             listeners: {
16302                 beforeanimate: {
16303                     fn: beforeAnim
16304                 }
16305             }
16306         });
16307         return me;
16308     },
16309
16310     /**
16311      * Shows a ripple of exploding, attenuating borders to draw attention to an Element. Usage:
16312      *
16313      *     // default: a single light blue ripple
16314      *     el.frame();
16315      *
16316      *     // custom: 3 red ripples lasting 3 seconds total
16317      *     el.frame("#ff0000", 3, { duration: 3 });
16318      *
16319      *     // common config options shown with default values
16320      *     el.frame("#C3DAF9", 1, {
16321      *         duration: 1 //duration of each individual ripple.
16322      *         // Note: Easing is not configurable and will be ignored if included
16323      *     });
16324      *
16325      * @param {String} [color='C3DAF9'] The color of the border. Should be a 6 char hex color without the leading #
16326      * (defaults to light blue).
16327      * @param {Number} [count=1] The number of ripples to display
16328      * @param {Object} [options] Object literal with any of the Fx config options
16329      * @return {Ext.Element} The Element
16330      */
16331     frame : function(color, count, obj){
16332         var me = this,
16333             beforeAnim;
16334
16335         color = color || '#C3DAF9';
16336         count = count || 1;
16337         obj = obj || {};
16338
16339         beforeAnim = function() {
16340             me.show();
16341             var animScope = this,
16342                 box = me.getBox(),
16343                 proxy = Ext.getBody().createChild({
16344                     style: {
16345                         position : 'absolute',
16346                         'pointer-events': 'none',
16347                         'z-index': 35000,
16348                         border : '0px solid ' + color
16349                     }
16350                 }),
16351                 proxyAnim;
16352             proxyAnim = Ext.create('Ext.fx.Anim', {
16353                 target: proxy,
16354                 duration: obj.duration || 1000,
16355                 iterations: count,
16356                 from: {
16357                     top: box.y,
16358                     left: box.x,
16359                     borderWidth: 0,
16360                     opacity: 1,
16361                     height: box.height,
16362                     width: box.width
16363                 },
16364                 to: {
16365                     top: box.y - 20,
16366                     left: box.x - 20,
16367                     borderWidth: 10,
16368                     opacity: 0,
16369                     height: box.height + 40,
16370                     width: box.width + 40
16371                 }
16372             });
16373             proxyAnim.on('afteranimate', function() {
16374                 proxy.remove();
16375                 animScope.end();
16376             });
16377         };
16378
16379         me.animate({
16380             duration: (obj.duration * 2) || 2000,
16381             listeners: {
16382                 beforeanimate: {
16383                     fn: beforeAnim
16384                 }
16385             }
16386         });
16387         return me;
16388     },
16389
16390     /**
16391      * Slides the element while fading it out of view. An anchor point can be optionally passed to set the ending point
16392      * of the effect. Usage:
16393      *
16394      *     // default: slide the element downward while fading out
16395      *     el.ghost();
16396      *
16397      *     // custom: slide the element out to the right with a 2-second duration
16398      *     el.ghost('r', { duration: 2000 });
16399      *
16400      *     // common config options shown with default values
16401      *     el.ghost('b', {
16402      *         easing: 'easeOut',
16403      *         duration: 500
16404      *     });
16405      *
16406      * @param {String} [anchor='b'] One of the valid Fx anchor positions
16407      * @param {Object} [options] Object literal with any of the Fx config options
16408      * @return {Ext.Element} The Element
16409      */
16410     ghost: function(anchor, obj) {
16411         var me = this,
16412             beforeAnim;
16413
16414         anchor = anchor || "b";
16415         beforeAnim = function() {
16416             var width = me.getWidth(),
16417                 height = me.getHeight(),
16418                 xy = me.getXY(),
16419                 position = me.getPositioning(),
16420                 to = {
16421                     opacity: 0
16422                 };
16423             switch (anchor) {
16424                 case 't':
16425                     to.y = xy[1] - height;
16426                     break;
16427                 case 'l':
16428                     to.x = xy[0] - width;
16429                     break;
16430                 case 'r':
16431                     to.x = xy[0] + width;
16432                     break;
16433                 case 'b':
16434                     to.y = xy[1] + height;
16435                     break;
16436                 case 'tl':
16437                     to.x = xy[0] - width;
16438                     to.y = xy[1] - height;
16439                     break;
16440                 case 'bl':
16441                     to.x = xy[0] - width;
16442                     to.y = xy[1] + height;
16443                     break;
16444                 case 'br':
16445                     to.x = xy[0] + width;
16446                     to.y = xy[1] + height;
16447                     break;
16448                 case 'tr':
16449                     to.x = xy[0] + width;
16450                     to.y = xy[1] - height;
16451                     break;
16452             }
16453             this.to = to;
16454             this.on('afteranimate', function () {
16455                 if (me.dom) {
16456                     me.hide();
16457                     me.clearOpacity();
16458                     me.setPositioning(position);
16459                 }
16460             });
16461         };
16462
16463         me.animate(Ext.applyIf(obj || {}, {
16464             duration: 500,
16465             easing: 'ease-out',
16466             listeners: {
16467                 beforeanimate: {
16468                     fn: beforeAnim
16469                 }
16470             }
16471         }));
16472         return me;
16473     },
16474
16475     /**
16476      * Highlights the Element by setting a color (applies to the background-color by default, but can be changed using
16477      * the "attr" config option) and then fading back to the original color. If no original color is available, you
16478      * should provide the "endColor" config option which will be cleared after the animation. Usage:
16479      *
16480      *     // default: highlight background to yellow
16481      *     el.highlight();
16482      *
16483      *     // custom: highlight foreground text to blue for 2 seconds
16484      *     el.highlight("0000ff", { attr: 'color', duration: 2000 });
16485      *
16486      *     // common config options shown with default values
16487      *     el.highlight("ffff9c", {
16488      *         attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
16489      *         endColor: (current color) or "ffffff",
16490      *         easing: 'easeIn',
16491      *         duration: 1000
16492      *     });
16493      *
16494      * @param {String} [color='ffff9c'] The highlight color. Should be a 6 char hex color without the leading #
16495      * @param {Object} [options] Object literal with any of the Fx config options
16496      * @return {Ext.Element} The Element
16497      */
16498     highlight: function(color, o) {
16499         var me = this,
16500             dom = me.dom,
16501             from = {},
16502             restore, to, attr, lns, event, fn;
16503
16504         o = o || {};
16505         lns = o.listeners || {};
16506         attr = o.attr || 'backgroundColor';
16507         from[attr] = color || 'ffff9c';
16508
16509         if (!o.to) {
16510             to = {};
16511             to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
16512         }
16513         else {
16514             to = o.to;
16515         }
16516
16517         // Don't apply directly on lns, since we reference it in our own callbacks below
16518         o.listeners = Ext.apply(Ext.apply({}, lns), {
16519             beforeanimate: function() {
16520                 restore = dom.style[attr];
16521                 me.clearOpacity();
16522                 me.show();
16523
16524                 event = lns.beforeanimate;
16525                 if (event) {
16526                     fn = event.fn || event;
16527                     return fn.apply(event.scope || lns.scope || window, arguments);
16528                 }
16529             },
16530             afteranimate: function() {
16531                 if (dom) {
16532                     dom.style[attr] = restore;
16533                 }
16534
16535                 event = lns.afteranimate;
16536                 if (event) {
16537                     fn = event.fn || event;
16538                     fn.apply(event.scope || lns.scope || window, arguments);
16539                 }
16540             }
16541         });
16542
16543         me.animate(Ext.apply({}, o, {
16544             duration: 1000,
16545             easing: 'ease-in',
16546             from: from,
16547             to: to
16548         }));
16549         return me;
16550     },
16551
16552    /**
16553     * @deprecated 4.0
16554     * Creates a pause before any subsequent queued effects begin. If there are no effects queued after the pause it will
16555     * have no effect. Usage:
16556     *
16557     *     el.pause(1);
16558     *
16559     * @param {Number} seconds The length of time to pause (in seconds)
16560     * @return {Ext.Element} The Element
16561     */
16562     pause: function(ms) {
16563         var me = this;
16564         Ext.fx.Manager.setFxDefaults(me.id, {
16565             delay: ms
16566         });
16567         return me;
16568     },
16569
16570     /**
16571      * Fade an element in (from transparent to opaque). The ending opacity can be specified using the `opacity`
16572      * config option. Usage:
16573      *
16574      *     // default: fade in from opacity 0 to 100%
16575      *     el.fadeIn();
16576      *
16577      *     // custom: fade in from opacity 0 to 75% over 2 seconds
16578      *     el.fadeIn({ opacity: .75, duration: 2000});
16579      *
16580      *     // common config options shown with default values
16581      *     el.fadeIn({
16582      *         opacity: 1, //can be any value between 0 and 1 (e.g. .5)
16583      *         easing: 'easeOut',
16584      *         duration: 500
16585      *     });
16586      *
16587      * @param {Object} options (optional) Object literal with any of the Fx config options
16588      * @return {Ext.Element} The Element
16589      */
16590     fadeIn: function(o) {
16591         this.animate(Ext.apply({}, o, {
16592             opacity: 1
16593         }));
16594         return this;
16595     },
16596
16597     /**
16598      * Fade an element out (from opaque to transparent). The ending opacity can be specified using the `opacity`
16599      * config option. Note that IE may require `useDisplay:true` in order to redisplay correctly.
16600      * Usage:
16601      *
16602      *     // default: fade out from the element's current opacity to 0
16603      *     el.fadeOut();
16604      *
16605      *     // custom: fade out from the element's current opacity to 25% over 2 seconds
16606      *     el.fadeOut({ opacity: .25, duration: 2000});
16607      *
16608      *     // common config options shown with default values
16609      *     el.fadeOut({
16610      *         opacity: 0, //can be any value between 0 and 1 (e.g. .5)
16611      *         easing: 'easeOut',
16612      *         duration: 500,
16613      *         remove: false,
16614      *         useDisplay: false
16615      *     });
16616      *
16617      * @param {Object} options (optional) Object literal with any of the Fx config options
16618      * @return {Ext.Element} The Element
16619      */
16620     fadeOut: function(o) {
16621         this.animate(Ext.apply({}, o, {
16622             opacity: 0
16623         }));
16624         return this;
16625     },
16626
16627     /**
16628      * @deprecated 4.0
16629      * Animates the transition of an element's dimensions from a starting height/width to an ending height/width. This
16630      * method is a convenience implementation of {@link #shift}. Usage:
16631      *
16632      *     // change height and width to 100x100 pixels
16633      *     el.scale(100, 100);
16634      *
16635      *     // common config options shown with default values.  The height and width will default to
16636      *     // the element's existing values if passed as null.
16637      *     el.scale(
16638      *         [element's width],
16639      *         [element's height], {
16640      *             easing: 'easeOut',
16641      *             duration: .35
16642      *         }
16643      *     );
16644      *
16645      * @param {Number} width The new width (pass undefined to keep the original width)
16646      * @param {Number} height The new height (pass undefined to keep the original height)
16647      * @param {Object} options (optional) Object literal with any of the Fx config options
16648      * @return {Ext.Element} The Element
16649      */
16650     scale: function(w, h, o) {
16651         this.animate(Ext.apply({}, o, {
16652             width: w,
16653             height: h
16654         }));
16655         return this;
16656     },
16657
16658     /**
16659      * @deprecated 4.0
16660      * Animates the transition of any combination of an element's dimensions, xy position and/or opacity. Any of these
16661      * properties not specified in the config object will not be changed. This effect requires that at least one new
16662      * dimension, position or opacity setting must be passed in on the config object in order for the function to have
16663      * any effect. Usage:
16664      *
16665      *     // slide the element horizontally to x position 200 while changing the height and opacity
16666      *     el.shift({ x: 200, height: 50, opacity: .8 });
16667      *
16668      *     // common config options shown with default values.
16669      *     el.shift({
16670      *         width: [element's width],
16671      *         height: [element's height],
16672      *         x: [element's x position],
16673      *         y: [element's y position],
16674      *         opacity: [element's opacity],
16675      *         easing: 'easeOut',
16676      *         duration: .35
16677      *     });
16678      *
16679      * @param {Object} options Object literal with any of the Fx config options
16680      * @return {Ext.Element} The Element
16681      */
16682     shift: function(config) {
16683         this.animate(config);
16684         return this;
16685     }
16686 });
16687
16688 /**
16689  * @class Ext.Element
16690  */
16691 Ext.applyIf(Ext.Element, {
16692     unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
16693     camelRe: /(-[a-z])/gi,
16694     opacityRe: /alpha\(opacity=(.*)\)/i,
16695     cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
16696     propertyCache: {},
16697     defaultUnit : "px",
16698     borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
16699     paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
16700     margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
16701
16702     // Reference the prototype's version of the method. Signatures are identical.
16703     addUnits : Ext.Element.prototype.addUnits,
16704
16705     /**
16706      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16707      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16708      * @static
16709      * @param {Number/String} box The encoded margins
16710      * @return {Object} An object with margin sizes for top, right, bottom and left
16711      */
16712     parseBox : function(box) {
16713         if (Ext.isObject(box)) {
16714             return {
16715                 top: box.top || 0,
16716                 right: box.right || 0,
16717                 bottom: box.bottom || 0,
16718                 left: box.left || 0
16719             };
16720         } else {
16721             if (typeof box != 'string') {
16722                 box = box.toString();
16723             }
16724             var parts  = box.split(' '),
16725                 ln = parts.length;
16726     
16727             if (ln == 1) {
16728                 parts[1] = parts[2] = parts[3] = parts[0];
16729             }
16730             else if (ln == 2) {
16731                 parts[2] = parts[0];
16732                 parts[3] = parts[1];
16733             }
16734             else if (ln == 3) {
16735                 parts[3] = parts[1];
16736             }
16737     
16738             return {
16739                 top   :parseFloat(parts[0]) || 0,
16740                 right :parseFloat(parts[1]) || 0,
16741                 bottom:parseFloat(parts[2]) || 0,
16742                 left  :parseFloat(parts[3]) || 0
16743             };
16744         }
16745         
16746     },
16747     
16748     /**
16749      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16750      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16751      * @static
16752      * @param {Number/String} box The encoded margins
16753      * @param {String} units The type of units to add
16754      * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
16755      */
16756     unitizeBox : function(box, units) {
16757         var A = this.addUnits,
16758             B = this.parseBox(box);
16759             
16760         return A(B.top, units) + ' ' +
16761                A(B.right, units) + ' ' +
16762                A(B.bottom, units) + ' ' +
16763                A(B.left, units);
16764         
16765     },
16766
16767     // private
16768     camelReplaceFn : function(m, a) {
16769         return a.charAt(1).toUpperCase();
16770     },
16771
16772     /**
16773      * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
16774      * For example:
16775      * <ul>
16776      *  <li>border-width -> borderWidth</li>
16777      *  <li>padding-top -> paddingTop</li>
16778      * </ul>
16779      * @static
16780      * @param {String} prop The property to normalize
16781      * @return {String} The normalized string
16782      */
16783     normalize : function(prop) {
16784         if (prop == 'float') {
16785             prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
16786         }
16787         return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
16788     },
16789
16790     /**
16791      * Retrieves the document height
16792      * @static
16793      * @return {Number} documentHeight
16794      */
16795     getDocumentHeight: function() {
16796         return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
16797     },
16798
16799     /**
16800      * Retrieves the document width
16801      * @static
16802      * @return {Number} documentWidth
16803      */
16804     getDocumentWidth: function() {
16805         return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
16806     },
16807
16808     /**
16809      * Retrieves the viewport height of the window.
16810      * @static
16811      * @return {Number} viewportHeight
16812      */
16813     getViewportHeight: function(){
16814         return window.innerHeight;
16815     },
16816
16817     /**
16818      * Retrieves the viewport width of the window.
16819      * @static
16820      * @return {Number} viewportWidth
16821      */
16822     getViewportWidth : function() {
16823         return window.innerWidth;
16824     },
16825
16826     /**
16827      * Retrieves the viewport size of the window.
16828      * @static
16829      * @return {Object} object containing width and height properties
16830      */
16831     getViewSize : function() {
16832         return {
16833             width: window.innerWidth,
16834             height: window.innerHeight
16835         };
16836     },
16837
16838     /**
16839      * Retrieves the current orientation of the window. This is calculated by
16840      * determing if the height is greater than the width.
16841      * @static
16842      * @return {String} Orientation of window: 'portrait' or 'landscape'
16843      */
16844     getOrientation : function() {
16845         if (Ext.supports.OrientationChange) {
16846             return (window.orientation == 0) ? 'portrait' : 'landscape';
16847         }
16848         
16849         return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
16850     },
16851
16852     /** 
16853      * Returns the top Element that is located at the passed coordinates
16854      * @static
16855      * @param {Number} x The x coordinate
16856      * @param {Number} y The y coordinate
16857      * @return {String} The found Element
16858      */
16859     fromPoint: function(x, y) {
16860         return Ext.get(document.elementFromPoint(x, y));
16861     },
16862     
16863     /**
16864      * Converts a CSS string into an object with a property for each style.
16865      * <p>
16866      * The sample code below would return an object with 2 properties, one
16867      * for background-color and one for color.</p>
16868      * <pre><code>
16869 var css = 'background-color: red;color: blue; ';
16870 console.log(Ext.Element.parseStyles(css));
16871      * </code></pre>
16872      * @static
16873      * @param {String} styles A CSS string
16874      * @return {Object} styles
16875      */
16876     parseStyles: function(styles){
16877         var out = {},
16878             cssRe = this.cssRe,
16879             matches;
16880             
16881         if (styles) {
16882             // Since we're using the g flag on the regex, we need to set the lastIndex.
16883             // This automatically happens on some implementations, but not others, see:
16884             // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
16885             // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
16886             cssRe.lastIndex = 0;
16887             while ((matches = cssRe.exec(styles))) {
16888                 out[matches[1]] = matches[2];
16889             }
16890         }
16891         return out;
16892     }
16893 });
16894
16895 /**
16896  * @class Ext.CompositeElementLite
16897  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
16898  * members, or to perform collective actions upon the whole set.</p>
16899  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
16900  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
16901  * Example:<pre><code>
16902 var els = Ext.select("#some-el div.some-class");
16903 // or select directly from an existing element
16904 var el = Ext.get('some-el');
16905 el.select('div.some-class');
16906
16907 els.setWidth(100); // all elements become 100 width
16908 els.hide(true); // all elements fade out and hide
16909 // or
16910 els.setWidth(100).hide(true);
16911 </code></pre>
16912  */
16913 Ext.CompositeElementLite = function(els, root){
16914     /**
16915      * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
16916      * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
16917      * to augment the capabilities of the CompositeElementLite class may use it when adding
16918      * methods to the class.</p>
16919      * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
16920      * following siblings of selected elements, the code would be</p><code><pre>
16921 Ext.override(Ext.CompositeElementLite, {
16922     nextAll: function() {
16923         var els = this.elements, i, l = els.length, n, r = [], ri = -1;
16924
16925 //      Loop through all elements in this Composite, accumulating
16926 //      an Array of all siblings.
16927         for (i = 0; i < l; i++) {
16928             for (n = els[i].nextSibling; n; n = n.nextSibling) {
16929                 r[++ri] = n;
16930             }
16931         }
16932
16933 //      Add all found siblings to this Composite
16934         return this.add(r);
16935     }
16936 });</pre></code>
16937      * @property {HTMLElement} elements
16938      */
16939     this.elements = [];
16940     this.add(els, root);
16941     this.el = new Ext.Element.Flyweight();
16942 };
16943
16944 Ext.CompositeElementLite.prototype = {
16945     isComposite: true,
16946
16947     // private
16948     getElement : function(el){
16949         // Set the shared flyweight dom property to the current element
16950         var e = this.el;
16951         e.dom = el;
16952         e.id = el.id;
16953         return e;
16954     },
16955
16956     // private
16957     transformElement : function(el){
16958         return Ext.getDom(el);
16959     },
16960
16961     /**
16962      * Returns the number of elements in this Composite.
16963      * @return Number
16964      */
16965     getCount : function(){
16966         return this.elements.length;
16967     },
16968     /**
16969      * Adds elements to this Composite object.
16970      * @param {HTMLElement[]/Ext.CompositeElement} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
16971      * @return {Ext.CompositeElement} This Composite object.
16972      */
16973     add : function(els, root){
16974         var me = this,
16975             elements = me.elements;
16976         if(!els){
16977             return this;
16978         }
16979         if(typeof els == "string"){
16980             els = Ext.Element.selectorFunction(els, root);
16981         }else if(els.isComposite){
16982             els = els.elements;
16983         }else if(!Ext.isIterable(els)){
16984             els = [els];
16985         }
16986
16987         for(var i = 0, len = els.length; i < len; ++i){
16988             elements.push(me.transformElement(els[i]));
16989         }
16990         return me;
16991     },
16992
16993     invoke : function(fn, args){
16994         var me = this,
16995             els = me.elements,
16996             len = els.length,
16997             e,
16998             i;
16999
17000         for(i = 0; i < len; i++) {
17001             e = els[i];
17002             if(e){
17003                 Ext.Element.prototype[fn].apply(me.getElement(e), args);
17004             }
17005         }
17006         return me;
17007     },
17008     /**
17009      * Returns a flyweight Element of the dom element object at the specified index
17010      * @param {Number} index
17011      * @return {Ext.Element}
17012      */
17013     item : function(index){
17014         var me = this,
17015             el = me.elements[index],
17016             out = null;
17017
17018         if(el){
17019             out = me.getElement(el);
17020         }
17021         return out;
17022     },
17023
17024     // fixes scope with flyweight
17025     addListener : function(eventName, handler, scope, opt){
17026         var els = this.elements,
17027             len = els.length,
17028             i, e;
17029
17030         for(i = 0; i<len; i++) {
17031             e = els[i];
17032             if(e) {
17033                 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
17034             }
17035         }
17036         return this;
17037     },
17038     /**
17039      * <p>Calls the passed function for each element in this composite.</p>
17040      * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
17041      * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
17042      * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
17043      * a reference to the dom node, use el.dom.</b></div></li>
17044      * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
17045      * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
17046      * </ul>
17047      * @param {Object} [scope] The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
17048      * @return {Ext.CompositeElement} this
17049      */
17050     each : function(fn, scope){
17051         var me = this,
17052             els = me.elements,
17053             len = els.length,
17054             i, e;
17055
17056         for(i = 0; i<len; i++) {
17057             e = els[i];
17058             if(e){
17059                 e = this.getElement(e);
17060                 if(fn.call(scope || e, e, me, i) === false){
17061                     break;
17062                 }
17063             }
17064         }
17065         return me;
17066     },
17067
17068     /**
17069     * Clears this Composite and adds the elements passed.
17070     * @param {HTMLElement[]/Ext.CompositeElement} els Either an array of DOM elements, or another Composite from which to fill this Composite.
17071     * @return {Ext.CompositeElement} this
17072     */
17073     fill : function(els){
17074         var me = this;
17075         me.elements = [];
17076         me.add(els);
17077         return me;
17078     },
17079
17080     /**
17081      * Filters this composite to only elements that match the passed selector.
17082      * @param {String/Function} selector A string CSS selector or a comparison function.
17083      * The comparison function will be called with the following arguments:<ul>
17084      * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
17085      * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
17086      * </ul>
17087      * @return {Ext.CompositeElement} this
17088      */
17089     filter : function(selector){
17090         var els = [],
17091             me = this,
17092             fn = Ext.isFunction(selector) ? selector
17093                 : function(el){
17094                     return el.is(selector);
17095                 };
17096
17097         me.each(function(el, self, i) {
17098             if (fn(el, i) !== false) {
17099                 els[els.length] = me.transformElement(el);
17100             }
17101         });
17102
17103         me.elements = els;
17104         return me;
17105     },
17106
17107     /**
17108      * Find the index of the passed element within the composite collection.
17109      * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
17110      * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
17111      */
17112     indexOf : function(el){
17113         return Ext.Array.indexOf(this.elements, this.transformElement(el));
17114     },
17115
17116     /**
17117     * Replaces the specified element with the passed element.
17118     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
17119     * to replace.
17120     * @param {String/Ext.Element} replacement The id of an element or the Element itself.
17121     * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
17122     * @return {Ext.CompositeElement} this
17123     */
17124     replaceElement : function(el, replacement, domReplace){
17125         var index = !isNaN(el) ? el : this.indexOf(el),
17126             d;
17127         if(index > -1){
17128             replacement = Ext.getDom(replacement);
17129             if(domReplace){
17130                 d = this.elements[index];
17131                 d.parentNode.insertBefore(replacement, d);
17132                 Ext.removeNode(d);
17133             }
17134             Ext.Array.splice(this.elements, index, 1, replacement);
17135         }
17136         return this;
17137     },
17138
17139     /**
17140      * Removes all elements.
17141      */
17142     clear : function(){
17143         this.elements = [];
17144     }
17145 };
17146
17147 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
17148
17149 /**
17150  * @private
17151  * Copies all of the functions from Ext.Element's prototype onto CompositeElementLite's prototype.
17152  * This is called twice - once immediately below, and once again after additional Ext.Element
17153  * are added in Ext JS
17154  */
17155 Ext.CompositeElementLite.importElementMethods = function() {
17156     var fnName,
17157         ElProto = Ext.Element.prototype,
17158         CelProto = Ext.CompositeElementLite.prototype;
17159
17160     for (fnName in ElProto) {
17161         if (typeof ElProto[fnName] == 'function'){
17162             (function(fnName) {
17163                 CelProto[fnName] = CelProto[fnName] || function() {
17164                     return this.invoke(fnName, arguments);
17165                 };
17166             }).call(CelProto, fnName);
17167
17168         }
17169     }
17170 };
17171
17172 Ext.CompositeElementLite.importElementMethods();
17173
17174 if(Ext.DomQuery){
17175     Ext.Element.selectorFunction = Ext.DomQuery.select;
17176 }
17177
17178 /**
17179  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
17180  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
17181  * {@link Ext.CompositeElementLite CompositeElementLite} object.
17182  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
17183  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
17184  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
17185  * @member Ext.Element
17186  * @method select
17187  */
17188 Ext.Element.select = function(selector, root){
17189     var els;
17190     if(typeof selector == "string"){
17191         els = Ext.Element.selectorFunction(selector, root);
17192     }else if(selector.length !== undefined){
17193         els = selector;
17194     }else{
17195         Ext.Error.raise({
17196             sourceClass: "Ext.Element",
17197             sourceMethod: "select",
17198             selector: selector,
17199             root: root,
17200             msg: "Invalid selector specified: " + selector
17201         });
17202     }
17203     return new Ext.CompositeElementLite(els);
17204 };
17205 /**
17206  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
17207  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
17208  * {@link Ext.CompositeElementLite CompositeElementLite} object.
17209  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
17210  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
17211  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
17212  * @member Ext
17213  * @method select
17214  */
17215 Ext.select = Ext.Element.select;
17216
17217 /**
17218  * @class Ext.util.DelayedTask
17219  * 
17220  * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
17221  * performing setTimeout where a new timeout cancels the old timeout. When called, the
17222  * task will wait the specified time period before executing. If durng that time period,
17223  * the task is called again, the original call will be cancelled. This continues so that
17224  * the function is only called a single time for each iteration.
17225  * 
17226  * This method is especially useful for things like detecting whether a user has finished
17227  * typing in a text field. An example would be performing validation on a keypress. You can
17228  * use this class to buffer the keypress events for a certain number of milliseconds, and
17229  * perform only if they stop for that amount of time.  
17230  * 
17231  * ## Usage
17232  * 
17233  *     var task = new Ext.util.DelayedTask(function(){
17234  *         alert(Ext.getDom('myInputField').value.length);
17235  *     });
17236  *     
17237  *     // Wait 500ms before calling our function. If the user presses another key
17238  *     // during that 500ms, it will be cancelled and we'll wait another 500ms.
17239  *     Ext.get('myInputField').on('keypress', function(){
17240  *         task.{@link #delay}(500);
17241  *     });
17242  * 
17243  * Note that we are using a DelayedTask here to illustrate a point. The configuration
17244  * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
17245  * also setup a delayed task for you to buffer events.
17246  * 
17247  * @constructor The parameters to this constructor serve as defaults and are not required.
17248  * @param {Function} fn (optional) The default function to call. If not specified here, it must be specified during the {@link #delay} call.
17249  * @param {Object} scope (optional) The default scope (The <code><b>this</b></code> reference) in which the
17250  * function is called. If not specified, <code>this</code> will refer to the browser window.
17251  * @param {Array} args (optional) The default Array of arguments.
17252  */
17253 Ext.util.DelayedTask = function(fn, scope, args) {
17254     var me = this,
17255         id,
17256         call = function() {
17257             clearInterval(id);
17258             id = null;
17259             fn.apply(scope, args || []);
17260         };
17261
17262     /**
17263      * Cancels any pending timeout and queues a new one
17264      * @param {Number} delay The milliseconds to delay
17265      * @param {Function} newFn (optional) Overrides function passed to constructor
17266      * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
17267      * is specified, <code>this</code> will refer to the browser window.
17268      * @param {Array} newArgs (optional) Overrides args passed to constructor
17269      */
17270     this.delay = function(delay, newFn, newScope, newArgs) {
17271         me.cancel();
17272         fn = newFn || fn;
17273         scope = newScope || scope;
17274         args = newArgs || args;
17275         id = setInterval(call, delay);
17276     };
17277
17278     /**
17279      * Cancel the last queued timeout
17280      */
17281     this.cancel = function(){
17282         if (id) {
17283             clearInterval(id);
17284             id = null;
17285         }
17286     };
17287 };
17288 Ext.require('Ext.util.DelayedTask', function() {
17289
17290     Ext.util.Event = Ext.extend(Object, (function() {
17291         function createBuffered(handler, listener, o, scope) {
17292             listener.task = new Ext.util.DelayedTask();
17293             return function() {
17294                 listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
17295             };
17296         }
17297
17298         function createDelayed(handler, listener, o, scope) {
17299             return function() {
17300                 var task = new Ext.util.DelayedTask();
17301                 if (!listener.tasks) {
17302                     listener.tasks = [];
17303                 }
17304                 listener.tasks.push(task);
17305                 task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
17306             };
17307         }
17308
17309         function createSingle(handler, listener, o, scope) {
17310             return function() {
17311                 listener.ev.removeListener(listener.fn, scope);
17312                 return handler.apply(scope, arguments);
17313             };
17314         }
17315
17316         return {
17317             isEvent: true,
17318
17319             constructor: function(observable, name) {
17320                 this.name = name;
17321                 this.observable = observable;
17322                 this.listeners = [];
17323             },
17324
17325             addListener: function(fn, scope, options) {
17326                 var me = this,
17327                     listener;
17328                     scope = scope || me.observable;
17329
17330                 if (!fn) {
17331                     Ext.Error.raise({
17332                         sourceClass: Ext.getClassName(this.observable),
17333                         sourceMethod: "addListener",
17334                         msg: "The specified callback function is undefined"
17335                     });
17336                 }
17337
17338                 if (!me.isListening(fn, scope)) {
17339                     listener = me.createListener(fn, scope, options);
17340                     if (me.firing) {
17341                         // if we are currently firing this event, don't disturb the listener loop
17342                         me.listeners = me.listeners.slice(0);
17343                     }
17344                     me.listeners.push(listener);
17345                 }
17346             },
17347
17348             createListener: function(fn, scope, o) {
17349                 o = o || {};
17350                 scope = scope || this.observable;
17351
17352                 var listener = {
17353                         fn: fn,
17354                         scope: scope,
17355                         o: o,
17356                         ev: this
17357                     },
17358                     handler = fn;
17359
17360                 // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
17361                 // because the event removal that the single listener does destroys the listener's DelayedTask(s)
17362                 if (o.single) {
17363                     handler = createSingle(handler, listener, o, scope);
17364                 }
17365                 if (o.delay) {
17366                     handler = createDelayed(handler, listener, o, scope);
17367                 }
17368                 if (o.buffer) {
17369                     handler = createBuffered(handler, listener, o, scope);
17370                 }
17371
17372                 listener.fireFn = handler;
17373                 return listener;
17374             },
17375
17376             findListener: function(fn, scope) {
17377                 var listeners = this.listeners,
17378                 i = listeners.length,
17379                 listener,
17380                 s;
17381
17382                 while (i--) {
17383                     listener = listeners[i];
17384                     if (listener) {
17385                         s = listener.scope;
17386                         if (listener.fn == fn && (s == scope || s == this.observable)) {
17387                             return i;
17388                         }
17389                     }
17390                 }
17391
17392                 return - 1;
17393             },
17394
17395             isListening: function(fn, scope) {
17396                 return this.findListener(fn, scope) !== -1;
17397             },
17398
17399             removeListener: function(fn, scope) {
17400                 var me = this,
17401                     index,
17402                     listener,
17403                     k;
17404                 index = me.findListener(fn, scope);
17405                 if (index != -1) {
17406                     listener = me.listeners[index];
17407
17408                     if (me.firing) {
17409                         me.listeners = me.listeners.slice(0);
17410                     }
17411
17412                     // cancel and remove a buffered handler that hasn't fired yet
17413                     if (listener.task) {
17414                         listener.task.cancel();
17415                         delete listener.task;
17416                     }
17417
17418                     // cancel and remove all delayed handlers that haven't fired yet
17419                     k = listener.tasks && listener.tasks.length;
17420                     if (k) {
17421                         while (k--) {
17422                             listener.tasks[k].cancel();
17423                         }
17424                         delete listener.tasks;
17425                     }
17426
17427                     // remove this listener from the listeners array
17428                     Ext.Array.erase(me.listeners, index, 1);
17429                     return true;
17430                 }
17431
17432                 return false;
17433             },
17434
17435             // Iterate to stop any buffered/delayed events
17436             clearListeners: function() {
17437                 var listeners = this.listeners,
17438                     i = listeners.length;
17439
17440                 while (i--) {
17441                     this.removeListener(listeners[i].fn, listeners[i].scope);
17442                 }
17443             },
17444
17445             fire: function() {
17446                 var me = this,
17447                     listeners = me.listeners,
17448                     count = listeners.length,
17449                     i,
17450                     args,
17451                     listener;
17452
17453                 if (count > 0) {
17454                     me.firing = true;
17455                     for (i = 0; i < count; i++) {
17456                         listener = listeners[i];
17457                         args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
17458                         if (listener.o) {
17459                             args.push(listener.o);
17460                         }
17461                         if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
17462                             return (me.firing = false);
17463                         }
17464                     }
17465                 }
17466                 me.firing = false;
17467                 return true;
17468             }
17469         };
17470     })());
17471 });
17472
17473 /**
17474  * @class Ext.EventManager
17475  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
17476  * several useful events directly.
17477  * See {@link Ext.EventObject} for more details on normalized event objects.
17478  * @singleton
17479  */
17480 Ext.EventManager = {
17481
17482     // --------------------- onReady ---------------------
17483
17484     /**
17485      * Check if we have bound our global onReady listener
17486      * @private
17487      */
17488     hasBoundOnReady: false,
17489
17490     /**
17491      * Check if fireDocReady has been called
17492      * @private
17493      */
17494     hasFiredReady: false,
17495
17496     /**
17497      * Timer for the document ready event in old IE versions
17498      * @private
17499      */
17500     readyTimeout: null,
17501
17502     /**
17503      * Checks if we have bound an onreadystatechange event
17504      * @private
17505      */
17506     hasOnReadyStateChange: false,
17507
17508     /**
17509      * Holds references to any onReady functions
17510      * @private
17511      */
17512     readyEvent: new Ext.util.Event(),
17513
17514     /**
17515      * Check the ready state for old IE versions
17516      * @private
17517      * @return {Boolean} True if the document is ready
17518      */
17519     checkReadyState: function(){
17520         var me = Ext.EventManager;
17521
17522         if(window.attachEvent){
17523             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
17524             // licensed courtesy of http://developer.yahoo.com/yui/license.html
17525             if (window != top) {
17526                 return false;
17527             }
17528             try{
17529                 document.documentElement.doScroll('left');
17530             }catch(e){
17531                 return false;
17532             }
17533             me.fireDocReady();
17534             return true;
17535         }
17536         if (document.readyState == 'complete') {
17537             me.fireDocReady();
17538             return true;
17539         }
17540         me.readyTimeout = setTimeout(arguments.callee, 2);
17541         return false;
17542     },
17543
17544     /**
17545      * Binds the appropriate browser event for checking if the DOM has loaded.
17546      * @private
17547      */
17548     bindReadyEvent: function(){
17549         var me = Ext.EventManager;
17550         if (me.hasBoundOnReady) {
17551             return;
17552         }
17553
17554         if (document.addEventListener) {
17555             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
17556             // fallback, load will ~always~ fire
17557             window.addEventListener('load', me.fireDocReady, false);
17558         } else {
17559             // check if the document is ready, this will also kick off the scroll checking timer
17560             if (!me.checkReadyState()) {
17561                 document.attachEvent('onreadystatechange', me.checkReadyState);
17562                 me.hasOnReadyStateChange = true;
17563             }
17564             // fallback, onload will ~always~ fire
17565             window.attachEvent('onload', me.fireDocReady, false);
17566         }
17567         me.hasBoundOnReady = true;
17568     },
17569
17570     /**
17571      * We know the document is loaded, so trigger any onReady events.
17572      * @private
17573      */
17574     fireDocReady: function(){
17575         var me = Ext.EventManager;
17576
17577         // only unbind these events once
17578         if (!me.hasFiredReady) {
17579             me.hasFiredReady = true;
17580
17581             if (document.addEventListener) {
17582                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
17583                 window.removeEventListener('load', me.fireDocReady, false);
17584             } else {
17585                 if (me.readyTimeout !== null) {
17586                     clearTimeout(me.readyTimeout);
17587                 }
17588                 if (me.hasOnReadyStateChange) {
17589                     document.detachEvent('onreadystatechange', me.checkReadyState);
17590                 }
17591                 window.detachEvent('onload', me.fireDocReady);
17592             }
17593             Ext.supports.init();
17594         }
17595         if (!Ext.isReady) {
17596             Ext.isReady = true;
17597             me.onWindowUnload();
17598             me.readyEvent.fire();
17599         }
17600     },
17601
17602     /**
17603      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
17604      * accessed shorthanded as Ext.onReady().
17605      * @param {Function} fn The method the event invokes.
17606      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17607      * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}.
17608      */
17609     onDocumentReady: function(fn, scope, options){
17610         options = options || {};
17611         var me = Ext.EventManager,
17612             readyEvent = me.readyEvent;
17613
17614         // force single to be true so our event is only ever fired once.
17615         options.single = true;
17616
17617         // Document already loaded, let's just fire it
17618         if (Ext.isReady) {
17619             readyEvent.addListener(fn, scope, options);
17620             readyEvent.fire();
17621         } else {
17622             options.delay = options.delay || 1;
17623             readyEvent.addListener(fn, scope, options);
17624             me.bindReadyEvent();
17625         }
17626     },
17627
17628
17629     // --------------------- event binding ---------------------
17630
17631     /**
17632      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
17633      * @private
17634      */
17635     stoppedMouseDownEvent: new Ext.util.Event(),
17636
17637     /**
17638      * Options to parse for the 4th argument to addListener.
17639      * @private
17640      */
17641     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
17642
17643     /**
17644      * Get the id of the element. If one has not been assigned, automatically assign it.
17645      * @param {HTMLElement/Ext.Element} element The element to get the id for.
17646      * @return {String} id
17647      */
17648     getId : function(element) {
17649         var skipGarbageCollection = false,
17650             id;
17651
17652         element = Ext.getDom(element);
17653
17654         if (element === document || element === window) {
17655             id = element === document ? Ext.documentId : Ext.windowId;
17656         }
17657         else {
17658             id = Ext.id(element);
17659         }
17660         // skip garbage collection for special elements (window, document, iframes)
17661         if (element && (element.getElementById || element.navigator)) {
17662             skipGarbageCollection = true;
17663         }
17664
17665         if (!Ext.cache[id]){
17666             Ext.Element.addToCache(new Ext.Element(element), id);
17667             if (skipGarbageCollection) {
17668                 Ext.cache[id].skipGarbageCollection = true;
17669             }
17670         }
17671         return id;
17672     },
17673
17674     /**
17675      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
17676      * @private
17677      * @param {Object} element The element the event is for
17678      * @param {Object} event The event configuration
17679      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
17680      */
17681     prepareListenerConfig: function(element, config, isRemove){
17682         var me = this,
17683             propRe = me.propRe,
17684             key, value, args;
17685
17686         // loop over all the keys in the object
17687         for (key in config) {
17688             if (config.hasOwnProperty(key)) {
17689                 // if the key is something else then an event option
17690                 if (!propRe.test(key)) {
17691                     value = config[key];
17692                     // if the value is a function it must be something like click: function(){}, scope: this
17693                     // which means that there might be multiple event listeners with shared options
17694                     if (Ext.isFunction(value)) {
17695                         // shared options
17696                         args = [element, key, value, config.scope, config];
17697                     } else {
17698                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
17699                         args = [element, key, value.fn, value.scope, value];
17700                     }
17701
17702                     if (isRemove === true) {
17703                         me.removeListener.apply(this, args);
17704                     } else {
17705                         me.addListener.apply(me, args);
17706                     }
17707                 }
17708             }
17709         }
17710     },
17711
17712     /**
17713      * Normalize cross browser event differences
17714      * @private
17715      * @param {Object} eventName The event name
17716      * @param {Object} fn The function to execute
17717      * @return {Object} The new event name/function
17718      */
17719     normalizeEvent: function(eventName, fn){
17720         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
17721             if (fn) {
17722                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
17723             }
17724             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
17725         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
17726             eventName = 'DOMMouseScroll';
17727         }
17728         return {
17729             eventName: eventName,
17730             fn: fn
17731         };
17732     },
17733
17734     /**
17735      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
17736      * @private
17737      * @param {Object} event
17738      */
17739     contains: function(event){
17740         var parent = event.browserEvent.currentTarget,
17741             child = this.getRelatedTarget(event);
17742
17743         if (parent && parent.firstChild) {
17744             while (child) {
17745                 if (child === parent) {
17746                     return false;
17747                 }
17748                 child = child.parentNode;
17749                 if (child && (child.nodeType != 1)) {
17750                     child = null;
17751                 }
17752             }
17753         }
17754         return true;
17755     },
17756
17757     /**
17758     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
17759     * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
17760     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
17761     * @param {String} eventName The name of the event to listen for.
17762     * @param {Function} handler The handler function the event invokes. This function is passed
17763     * the following parameters:<ul>
17764     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
17765     * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
17766     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
17767     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
17768     * </ul>
17769     * @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>.
17770     * @param {Object} options (optional) An object containing handler configuration properties.
17771     * This may contain any of the following properties:<ul>
17772     * <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>
17773     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
17774     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
17775     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
17776     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
17777     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
17778     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
17779     * <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>
17780     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
17781     * by the specified number of milliseconds. If the event fires again within that time, the original
17782     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
17783     * <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>
17784     * </ul><br>
17785     * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
17786     */
17787     addListener: function(element, eventName, fn, scope, options){
17788         // Check if we've been passed a "config style" event.
17789         if (typeof eventName !== 'string') {
17790             this.prepareListenerConfig(element, eventName);
17791             return;
17792         }
17793
17794         var dom = Ext.getDom(element),
17795             bind,
17796             wrap;
17797
17798         if (!dom){
17799             Ext.Error.raise({
17800                 sourceClass: 'Ext.EventManager',
17801                 sourceMethod: 'addListener',
17802                 targetElement: element,
17803                 eventName: eventName,
17804                 msg: 'Error adding "' + eventName + '\" listener for nonexistent element "' + element + '"'
17805             });
17806         }
17807         if (!fn) {
17808             Ext.Error.raise({
17809                 sourceClass: 'Ext.EventManager',
17810                 sourceMethod: 'addListener',
17811                 targetElement: element,
17812                 eventName: eventName,
17813                 msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
17814             });
17815         }
17816
17817         // create the wrapper function
17818         options = options || {};
17819
17820         bind = this.normalizeEvent(eventName, fn);
17821         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
17822
17823
17824         if (dom.attachEvent) {
17825             dom.attachEvent('on' + bind.eventName, wrap);
17826         } else {
17827             dom.addEventListener(bind.eventName, wrap, options.capture || false);
17828         }
17829
17830         if (dom == document && eventName == 'mousedown') {
17831             this.stoppedMouseDownEvent.addListener(wrap);
17832         }
17833
17834         // add all required data into the event cache
17835         this.getEventListenerCache(dom, eventName).push({
17836             fn: fn,
17837             wrap: wrap,
17838             scope: scope
17839         });
17840     },
17841
17842     /**
17843     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
17844     * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
17845     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
17846     * @param {String} eventName The name of the event.
17847     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
17848     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
17849     * then this must refer to the same object.
17850     */
17851     removeListener : function(element, eventName, fn, scope) {
17852         // handle our listener config object syntax
17853         if (typeof eventName !== 'string') {
17854             this.prepareListenerConfig(element, eventName, true);
17855             return;
17856         }
17857
17858         var dom = Ext.getDom(element),
17859             cache = this.getEventListenerCache(dom, eventName),
17860             bindName = this.normalizeEvent(eventName).eventName,
17861             i = cache.length, j,
17862             listener, wrap, tasks;
17863
17864
17865         while (i--) {
17866             listener = cache[i];
17867
17868             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
17869                 wrap = listener.wrap;
17870
17871                 // clear buffered calls
17872                 if (wrap.task) {
17873                     clearTimeout(wrap.task);
17874                     delete wrap.task;
17875                 }
17876
17877                 // clear delayed calls
17878                 j = wrap.tasks && wrap.tasks.length;
17879                 if (j) {
17880                     while (j--) {
17881                         clearTimeout(wrap.tasks[j]);
17882                     }
17883                     delete wrap.tasks;
17884                 }
17885
17886                 if (dom.detachEvent) {
17887                     dom.detachEvent('on' + bindName, wrap);
17888                 } else {
17889                     dom.removeEventListener(bindName, wrap, false);
17890                 }
17891
17892                 if (wrap && dom == document && eventName == 'mousedown') {
17893                     this.stoppedMouseDownEvent.removeListener(wrap);
17894                 }
17895
17896                 // remove listener from cache
17897                 Ext.Array.erase(cache, i, 1);
17898             }
17899         }
17900     },
17901
17902     /**
17903     * Removes all event handers from an element.  Typically you will use {@link Ext.Element#removeAllListeners}
17904     * directly on an Element in favor of calling this version.
17905     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
17906     */
17907     removeAll : function(element){
17908         var dom = Ext.getDom(element),
17909             cache, ev;
17910         if (!dom) {
17911             return;
17912         }
17913         cache = this.getElementEventCache(dom);
17914
17915         for (ev in cache) {
17916             if (cache.hasOwnProperty(ev)) {
17917                 this.removeListener(dom, ev);
17918             }
17919         }
17920         Ext.cache[dom.id].events = {};
17921     },
17922
17923     /**
17924      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.Element#purgeAllListeners}
17925      * directly on an Element in favor of calling this version.
17926      * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
17927      * @param {String} eventName (optional) The name of the event.
17928      */
17929     purgeElement : function(element, eventName) {
17930         var dom = Ext.getDom(element),
17931             i = 0, len;
17932
17933         if(eventName) {
17934             this.removeListener(dom, eventName);
17935         }
17936         else {
17937             this.removeAll(dom);
17938         }
17939
17940         if(dom && dom.childNodes) {
17941             for(len = element.childNodes.length; i < len; i++) {
17942                 this.purgeElement(element.childNodes[i], eventName);
17943             }
17944         }
17945     },
17946
17947     /**
17948      * Create the wrapper function for the event
17949      * @private
17950      * @param {HTMLElement} dom The dom element
17951      * @param {String} ename The event name
17952      * @param {Function} fn The function to execute
17953      * @param {Object} scope The scope to execute callback in
17954      * @param {Object} options The options
17955      * @return {Function} the wrapper function
17956      */
17957     createListenerWrap : function(dom, ename, fn, scope, options) {
17958         options = options || {};
17959
17960         var f, gen;
17961
17962         return function wrap(e, args) {
17963             // Compile the implementation upon first firing
17964             if (!gen) {
17965                 f = ['if(!Ext) {return;}'];
17966
17967                 if(options.buffer || options.delay || options.freezeEvent) {
17968                     f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
17969                 } else {
17970                     f.push('e = Ext.EventObject.setEvent(e);');
17971                 }
17972
17973                 if (options.delegate) {
17974                     f.push('var t = e.getTarget("' + options.delegate + '", this);');
17975                     f.push('if(!t) {return;}');
17976                 } else {
17977                     f.push('var t = e.target;');
17978                 }
17979
17980                 if (options.target) {
17981                     f.push('if(e.target !== options.target) {return;}');
17982                 }
17983
17984                 if(options.stopEvent) {
17985                     f.push('e.stopEvent();');
17986                 } else {
17987                     if(options.preventDefault) {
17988                         f.push('e.preventDefault();');
17989                     }
17990                     if(options.stopPropagation) {
17991                         f.push('e.stopPropagation();');
17992                     }
17993                 }
17994
17995                 if(options.normalized === false) {
17996                     f.push('e = e.browserEvent;');
17997                 }
17998
17999                 if(options.buffer) {
18000                     f.push('(wrap.task && clearTimeout(wrap.task));');
18001                     f.push('wrap.task = setTimeout(function(){');
18002                 }
18003
18004                 if(options.delay) {
18005                     f.push('wrap.tasks = wrap.tasks || [];');
18006                     f.push('wrap.tasks.push(setTimeout(function(){');
18007                 }
18008
18009                 // finally call the actual handler fn
18010                 f.push('fn.call(scope || dom, e, t, options);');
18011
18012                 if(options.single) {
18013                     f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
18014                 }
18015
18016                 if(options.delay) {
18017                     f.push('}, ' + options.delay + '));');
18018                 }
18019
18020                 if(options.buffer) {
18021                     f.push('}, ' + options.buffer + ');');
18022                 }
18023
18024                 gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
18025             }
18026
18027             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
18028         };
18029     },
18030
18031     /**
18032      * Get the event cache for a particular element for a particular event
18033      * @private
18034      * @param {HTMLElement} element The element
18035      * @param {Object} eventName The event name
18036      * @return {Array} The events for the element
18037      */
18038     getEventListenerCache : function(element, eventName) {
18039         if (!element) {
18040             return [];
18041         }
18042
18043         var eventCache = this.getElementEventCache(element);
18044         return eventCache[eventName] || (eventCache[eventName] = []);
18045     },
18046
18047     /**
18048      * Gets the event cache for the object
18049      * @private
18050      * @param {HTMLElement} element The element
18051      * @return {Object} The event cache for the object
18052      */
18053     getElementEventCache : function(element) {
18054         if (!element) {
18055             return {};
18056         }
18057         var elementCache = Ext.cache[this.getId(element)];
18058         return elementCache.events || (elementCache.events = {});
18059     },
18060
18061     // --------------------- utility methods ---------------------
18062     mouseLeaveRe: /(mouseout|mouseleave)/,
18063     mouseEnterRe: /(mouseover|mouseenter)/,
18064
18065     /**
18066      * Stop the event (preventDefault and stopPropagation)
18067      * @param {Event} The event to stop
18068      */
18069     stopEvent: function(event) {
18070         this.stopPropagation(event);
18071         this.preventDefault(event);
18072     },
18073
18074     /**
18075      * Cancels bubbling of the event.
18076      * @param {Event} The event to stop bubbling.
18077      */
18078     stopPropagation: function(event) {
18079         event = event.browserEvent || event;
18080         if (event.stopPropagation) {
18081             event.stopPropagation();
18082         } else {
18083             event.cancelBubble = true;
18084         }
18085     },
18086
18087     /**
18088      * Prevents the browsers default handling of the event.
18089      * @param {Event} The event to prevent the default
18090      */
18091     preventDefault: function(event) {
18092         event = event.browserEvent || event;
18093         if (event.preventDefault) {
18094             event.preventDefault();
18095         } else {
18096             event.returnValue = false;
18097             // Some keys events require setting the keyCode to -1 to be prevented
18098             try {
18099               // all ctrl + X and F1 -> F12
18100               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
18101                   event.keyCode = -1;
18102               }
18103             } catch (e) {
18104                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
18105             }
18106         }
18107     },
18108
18109     /**
18110      * Gets the related target from the event.
18111      * @param {Object} event The event
18112      * @return {HTMLElement} The related target.
18113      */
18114     getRelatedTarget: function(event) {
18115         event = event.browserEvent || event;
18116         var target = event.relatedTarget;
18117         if (!target) {
18118             if (this.mouseLeaveRe.test(event.type)) {
18119                 target = event.toElement;
18120             } else if (this.mouseEnterRe.test(event.type)) {
18121                 target = event.fromElement;
18122             }
18123         }
18124         return this.resolveTextNode(target);
18125     },
18126
18127     /**
18128      * Gets the x coordinate from the event
18129      * @param {Object} event The event
18130      * @return {Number} The x coordinate
18131      */
18132     getPageX: function(event) {
18133         return this.getXY(event)[0];
18134     },
18135
18136     /**
18137      * Gets the y coordinate from the event
18138      * @param {Object} event The event
18139      * @return {Number} The y coordinate
18140      */
18141     getPageY: function(event) {
18142         return this.getXY(event)[1];
18143     },
18144
18145     /**
18146      * Gets the x & y coordinate from the event
18147      * @param {Object} event The event
18148      * @return {Number[]} The x/y coordinate
18149      */
18150     getPageXY: function(event) {
18151         event = event.browserEvent || event;
18152         var x = event.pageX,
18153             y = event.pageY,
18154             doc = document.documentElement,
18155             body = document.body;
18156
18157         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
18158         if (!x && x !== 0) {
18159             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
18160             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
18161         }
18162         return [x, y];
18163     },
18164
18165     /**
18166      * Gets the target of the event.
18167      * @param {Object} event The event
18168      * @return {HTMLElement} target
18169      */
18170     getTarget: function(event) {
18171         event = event.browserEvent || event;
18172         return this.resolveTextNode(event.target || event.srcElement);
18173     },
18174
18175     /**
18176      * Resolve any text nodes accounting for browser differences.
18177      * @private
18178      * @param {HTMLElement} node The node
18179      * @return {HTMLElement} The resolved node
18180      */
18181     // 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.
18182     resolveTextNode: Ext.isGecko ?
18183         function(node) {
18184             if (!node) {
18185                 return;
18186             }
18187             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
18188             var s = HTMLElement.prototype.toString.call(node);
18189             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
18190                 return;
18191             }
18192                 return node.nodeType == 3 ? node.parentNode: node;
18193             }: function(node) {
18194                 return node && node.nodeType == 3 ? node.parentNode: node;
18195             },
18196
18197     // --------------------- custom event binding ---------------------
18198
18199     // Keep track of the current width/height
18200     curWidth: 0,
18201     curHeight: 0,
18202
18203     /**
18204      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
18205      * passes new viewport width and height to handlers.
18206      * @param {Function} fn      The handler function the window resize event invokes.
18207      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
18208      * @param {Boolean}  options Options object as passed to {@link Ext.Element#addListener}
18209      */
18210     onWindowResize: function(fn, scope, options){
18211         var resize = this.resizeEvent;
18212         if(!resize){
18213             this.resizeEvent = resize = new Ext.util.Event();
18214             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
18215         }
18216         resize.addListener(fn, scope, options);
18217     },
18218
18219     /**
18220      * Fire the resize event.
18221      * @private
18222      */
18223     fireResize: function(){
18224         var me = this,
18225             w = Ext.Element.getViewWidth(),
18226             h = Ext.Element.getViewHeight();
18227
18228          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
18229          if(me.curHeight != h || me.curWidth != w){
18230              me.curHeight = h;
18231              me.curWidth = w;
18232              me.resizeEvent.fire(w, h);
18233          }
18234     },
18235
18236     /**
18237      * Removes the passed window resize listener.
18238      * @param {Function} fn        The method the event invokes
18239      * @param {Object}   scope    The scope of handler
18240      */
18241     removeResizeListener: function(fn, scope){
18242         if (this.resizeEvent) {
18243             this.resizeEvent.removeListener(fn, scope);
18244         }
18245     },
18246
18247     onWindowUnload: function() {
18248         var unload = this.unloadEvent;
18249         if (!unload) {
18250             this.unloadEvent = unload = new Ext.util.Event();
18251             this.addListener(window, 'unload', this.fireUnload, this);
18252         }
18253     },
18254
18255     /**
18256      * Fires the unload event for items bound with onWindowUnload
18257      * @private
18258      */
18259     fireUnload: function() {
18260         // wrap in a try catch, could have some problems during unload
18261         try {
18262             this.removeUnloadListener();
18263             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
18264             if (Ext.isGecko3) {
18265                 var gridviews = Ext.ComponentQuery.query('gridview'),
18266                     i = 0,
18267                     ln = gridviews.length;
18268                 for (; i < ln; i++) {
18269                     gridviews[i].scrollToTop();
18270                 }
18271             }
18272             // Purge all elements in the cache
18273             var el,
18274                 cache = Ext.cache;
18275             for (el in cache) {
18276                 if (cache.hasOwnProperty(el)) {
18277                     Ext.EventManager.removeAll(el);
18278                 }
18279             }
18280         } catch(e) {
18281         }
18282     },
18283
18284     /**
18285      * Removes the passed window unload listener.
18286      * @param {Function} fn        The method the event invokes
18287      * @param {Object}   scope    The scope of handler
18288      */
18289     removeUnloadListener: function(){
18290         if (this.unloadEvent) {
18291             this.removeListener(window, 'unload', this.fireUnload);
18292         }
18293     },
18294
18295     /**
18296      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
18297      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
18298      * (research done by Jan Wolter at http://unixpapa.com/js/key.html)
18299      * @private
18300      */
18301     useKeyDown: Ext.isWebKit ?
18302                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
18303                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
18304
18305     /**
18306      * Indicates which event to use for getting key presses.
18307      * @return {String} The appropriate event name.
18308      */
18309     getKeyEvent: function(){
18310         return this.useKeyDown ? 'keydown' : 'keypress';
18311     }
18312 };
18313
18314 /**
18315  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
18316  * @member Ext
18317  * @method onReady
18318  */
18319 Ext.onReady = function(fn, scope, options) {
18320     Ext.Loader.onReady(fn, scope, true, options);
18321 };
18322
18323 /**
18324  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
18325  * @member Ext
18326  * @method onDocumentReady
18327  */
18328 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
18329
18330 /**
18331  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
18332  * @member Ext.EventManager
18333  * @method on
18334  */
18335 Ext.EventManager.on = Ext.EventManager.addListener;
18336
18337 /**
18338  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
18339  * @member Ext.EventManager
18340  * @method un
18341  */
18342 Ext.EventManager.un = Ext.EventManager.removeListener;
18343
18344 (function(){
18345     var initExtCss = function() {
18346         // find the body element
18347         var bd = document.body || document.getElementsByTagName('body')[0],
18348             baseCSSPrefix = Ext.baseCSSPrefix,
18349             cls = [baseCSSPrefix + 'body'],
18350             htmlCls = [],
18351             html;
18352
18353         if (!bd) {
18354             return false;
18355         }
18356
18357         html = bd.parentNode;
18358
18359         function add (c) {
18360             cls.push(baseCSSPrefix + c);
18361         }
18362
18363         //Let's keep this human readable!
18364         if (Ext.isIE) {
18365             add('ie');
18366
18367             // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
18368             // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
18369             // additional classes:
18370             //
18371             //      x-ie7p      : IE7+      :  7 <= ieVer
18372             //      x-ie7m      : IE7-      :  ieVer <= 7
18373             //      x-ie8p      : IE8+      :  8 <= ieVer
18374             //      x-ie8m      : IE8-      :  ieVer <= 8
18375             //      x-ie9p      : IE9+      :  9 <= ieVer
18376             //      x-ie78      : IE7 or 8  :  7 <= ieVer <= 8
18377             //
18378             if (Ext.isIE6) {
18379                 add('ie6');
18380             } else { // ignore pre-IE6 :)
18381                 add('ie7p');
18382
18383                 if (Ext.isIE7) {
18384                     add('ie7');
18385                 } else {
18386                     add('ie8p');
18387
18388                     if (Ext.isIE8) {
18389                         add('ie8');
18390                     } else {
18391                         add('ie9p');
18392
18393                         if (Ext.isIE9) {
18394                             add('ie9');
18395                         }
18396                     }
18397                 }
18398             }
18399
18400             if (Ext.isIE6 || Ext.isIE7) {
18401                 add('ie7m');
18402             }
18403             if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
18404                 add('ie8m');
18405             }
18406             if (Ext.isIE7 || Ext.isIE8) {
18407                 add('ie78');
18408             }
18409         }
18410         if (Ext.isGecko) {
18411             add('gecko');
18412             if (Ext.isGecko3) {
18413                 add('gecko3');
18414             }
18415             if (Ext.isGecko4) {
18416                 add('gecko4');
18417             }
18418             if (Ext.isGecko5) {
18419                 add('gecko5');
18420             }
18421         }
18422         if (Ext.isOpera) {
18423             add('opera');
18424         }
18425         if (Ext.isWebKit) {
18426             add('webkit');
18427         }
18428         if (Ext.isSafari) {
18429             add('safari');
18430             if (Ext.isSafari2) {
18431                 add('safari2');
18432             }
18433             if (Ext.isSafari3) {
18434                 add('safari3');
18435             }
18436             if (Ext.isSafari4) {
18437                 add('safari4');
18438             }
18439             if (Ext.isSafari5) {
18440                 add('safari5');
18441             }
18442         }
18443         if (Ext.isChrome) {
18444             add('chrome');
18445         }
18446         if (Ext.isMac) {
18447             add('mac');
18448         }
18449         if (Ext.isLinux) {
18450             add('linux');
18451         }
18452         if (!Ext.supports.CSS3BorderRadius) {
18453             add('nbr');
18454         }
18455         if (!Ext.supports.CSS3LinearGradient) {
18456             add('nlg');
18457         }
18458         if (!Ext.scopeResetCSS) {
18459             add('reset');
18460         }
18461
18462         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
18463         if (html) {
18464             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
18465                 Ext.isBorderBox = false;
18466             }
18467             else {
18468                 Ext.isBorderBox = true;
18469             }
18470
18471             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
18472             if (!Ext.isStrict) {
18473                 htmlCls.push(baseCSSPrefix + 'quirks');
18474             }
18475             Ext.fly(html, '_internal').addCls(htmlCls);
18476         }
18477
18478         Ext.fly(bd, '_internal').addCls(cls);
18479         return true;
18480     };
18481
18482     Ext.onReady(initExtCss);
18483 })();
18484
18485 /**
18486  * @class Ext.EventObject
18487
18488 Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
18489 wraps the browser's native event-object normalizing cross-browser differences,
18490 such as which mouse button is clicked, keys pressed, mechanisms to stop
18491 event-propagation along with a method to prevent default actions from taking place.
18492
18493 For example:
18494
18495     function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
18496         e.preventDefault();
18497         var target = e.getTarget(); // same as t (the target HTMLElement)
18498         ...
18499     }
18500
18501     var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.Element}
18502     myDiv.on(         // 'on' is shorthand for addListener
18503         "click",      // perform an action on click of myDiv
18504         handleClick   // reference to the action handler
18505     );
18506
18507     // other methods to do the same:
18508     Ext.EventManager.on("myDiv", 'click', handleClick);
18509     Ext.EventManager.addListener("myDiv", 'click', handleClick);
18510
18511  * @singleton
18512  * @markdown
18513  */
18514 Ext.define('Ext.EventObjectImpl', {
18515     uses: ['Ext.util.Point'],
18516
18517     /** Key constant @type Number */
18518     BACKSPACE: 8,
18519     /** Key constant @type Number */
18520     TAB: 9,
18521     /** Key constant @type Number */
18522     NUM_CENTER: 12,
18523     /** Key constant @type Number */
18524     ENTER: 13,
18525     /** Key constant @type Number */
18526     RETURN: 13,
18527     /** Key constant @type Number */
18528     SHIFT: 16,
18529     /** Key constant @type Number */
18530     CTRL: 17,
18531     /** Key constant @type Number */
18532     ALT: 18,
18533     /** Key constant @type Number */
18534     PAUSE: 19,
18535     /** Key constant @type Number */
18536     CAPS_LOCK: 20,
18537     /** Key constant @type Number */
18538     ESC: 27,
18539     /** Key constant @type Number */
18540     SPACE: 32,
18541     /** Key constant @type Number */
18542     PAGE_UP: 33,
18543     /** Key constant @type Number */
18544     PAGE_DOWN: 34,
18545     /** Key constant @type Number */
18546     END: 35,
18547     /** Key constant @type Number */
18548     HOME: 36,
18549     /** Key constant @type Number */
18550     LEFT: 37,
18551     /** Key constant @type Number */
18552     UP: 38,
18553     /** Key constant @type Number */
18554     RIGHT: 39,
18555     /** Key constant @type Number */
18556     DOWN: 40,
18557     /** Key constant @type Number */
18558     PRINT_SCREEN: 44,
18559     /** Key constant @type Number */
18560     INSERT: 45,
18561     /** Key constant @type Number */
18562     DELETE: 46,
18563     /** Key constant @type Number */
18564     ZERO: 48,
18565     /** Key constant @type Number */
18566     ONE: 49,
18567     /** Key constant @type Number */
18568     TWO: 50,
18569     /** Key constant @type Number */
18570     THREE: 51,
18571     /** Key constant @type Number */
18572     FOUR: 52,
18573     /** Key constant @type Number */
18574     FIVE: 53,
18575     /** Key constant @type Number */
18576     SIX: 54,
18577     /** Key constant @type Number */
18578     SEVEN: 55,
18579     /** Key constant @type Number */
18580     EIGHT: 56,
18581     /** Key constant @type Number */
18582     NINE: 57,
18583     /** Key constant @type Number */
18584     A: 65,
18585     /** Key constant @type Number */
18586     B: 66,
18587     /** Key constant @type Number */
18588     C: 67,
18589     /** Key constant @type Number */
18590     D: 68,
18591     /** Key constant @type Number */
18592     E: 69,
18593     /** Key constant @type Number */
18594     F: 70,
18595     /** Key constant @type Number */
18596     G: 71,
18597     /** Key constant @type Number */
18598     H: 72,
18599     /** Key constant @type Number */
18600     I: 73,
18601     /** Key constant @type Number */
18602     J: 74,
18603     /** Key constant @type Number */
18604     K: 75,
18605     /** Key constant @type Number */
18606     L: 76,
18607     /** Key constant @type Number */
18608     M: 77,
18609     /** Key constant @type Number */
18610     N: 78,
18611     /** Key constant @type Number */
18612     O: 79,
18613     /** Key constant @type Number */
18614     P: 80,
18615     /** Key constant @type Number */
18616     Q: 81,
18617     /** Key constant @type Number */
18618     R: 82,
18619     /** Key constant @type Number */
18620     S: 83,
18621     /** Key constant @type Number */
18622     T: 84,
18623     /** Key constant @type Number */
18624     U: 85,
18625     /** Key constant @type Number */
18626     V: 86,
18627     /** Key constant @type Number */
18628     W: 87,
18629     /** Key constant @type Number */
18630     X: 88,
18631     /** Key constant @type Number */
18632     Y: 89,
18633     /** Key constant @type Number */
18634     Z: 90,
18635     /** Key constant @type Number */
18636     CONTEXT_MENU: 93,
18637     /** Key constant @type Number */
18638     NUM_ZERO: 96,
18639     /** Key constant @type Number */
18640     NUM_ONE: 97,
18641     /** Key constant @type Number */
18642     NUM_TWO: 98,
18643     /** Key constant @type Number */
18644     NUM_THREE: 99,
18645     /** Key constant @type Number */
18646     NUM_FOUR: 100,
18647     /** Key constant @type Number */
18648     NUM_FIVE: 101,
18649     /** Key constant @type Number */
18650     NUM_SIX: 102,
18651     /** Key constant @type Number */
18652     NUM_SEVEN: 103,
18653     /** Key constant @type Number */
18654     NUM_EIGHT: 104,
18655     /** Key constant @type Number */
18656     NUM_NINE: 105,
18657     /** Key constant @type Number */
18658     NUM_MULTIPLY: 106,
18659     /** Key constant @type Number */
18660     NUM_PLUS: 107,
18661     /** Key constant @type Number */
18662     NUM_MINUS: 109,
18663     /** Key constant @type Number */
18664     NUM_PERIOD: 110,
18665     /** Key constant @type Number */
18666     NUM_DIVISION: 111,
18667     /** Key constant @type Number */
18668     F1: 112,
18669     /** Key constant @type Number */
18670     F2: 113,
18671     /** Key constant @type Number */
18672     F3: 114,
18673     /** Key constant @type Number */
18674     F4: 115,
18675     /** Key constant @type Number */
18676     F5: 116,
18677     /** Key constant @type Number */
18678     F6: 117,
18679     /** Key constant @type Number */
18680     F7: 118,
18681     /** Key constant @type Number */
18682     F8: 119,
18683     /** Key constant @type Number */
18684     F9: 120,
18685     /** Key constant @type Number */
18686     F10: 121,
18687     /** Key constant @type Number */
18688     F11: 122,
18689     /** Key constant @type Number */
18690     F12: 123,
18691     /**
18692      * The mouse wheel delta scaling factor. This value depends on browser version and OS and
18693      * attempts to produce a similar scrolling experience across all platforms and browsers.
18694      *
18695      * To change this value:
18696      *
18697      *      Ext.EventObjectImpl.prototype.WHEEL_SCALE = 72;
18698      *
18699      * @type Number
18700      * @markdown
18701      */
18702     WHEEL_SCALE: (function () {
18703         var scale;
18704
18705         if (Ext.isGecko) {
18706             // Firefox uses 3 on all platforms
18707             scale = 3;
18708         } else if (Ext.isMac) {
18709             // Continuous scrolling devices have momentum and produce much more scroll than
18710             // discrete devices on the same OS and browser. To make things exciting, Safari
18711             // (and not Chrome) changed from small values to 120 (like IE).
18712
18713             if (Ext.isSafari && Ext.webKitVersion >= 532.0) {
18714                 // Safari changed the scrolling factor to match IE (for details see
18715                 // https://bugs.webkit.org/show_bug.cgi?id=24368). The WebKit version where this
18716                 // change was introduced was 532.0
18717                 //      Detailed discussion:
18718                 //      https://bugs.webkit.org/show_bug.cgi?id=29601
18719                 //      http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
18720                 scale = 120;
18721             } else {
18722                 // MS optical wheel mouse produces multiples of 12 which is close enough
18723                 // to help tame the speed of the continuous mice...
18724                 scale = 12;
18725             }
18726
18727             // Momentum scrolling produces very fast scrolling, so increase the scale factor
18728             // to help produce similar results cross platform. This could be even larger and
18729             // it would help those mice, but other mice would become almost unusable as a
18730             // result (since we cannot tell which device type is in use).
18731             scale *= 3;
18732         } else {
18733             // IE, Opera and other Windows browsers use 120.
18734             scale = 120;
18735         }
18736
18737         return scale;
18738     })(),
18739
18740     /**
18741      * Simple click regex
18742      * @private
18743      */
18744     clickRe: /(dbl)?click/,
18745     // safari keypress events for special keys return bad keycodes
18746     safariKeys: {
18747         3: 13, // enter
18748         63234: 37, // left
18749         63235: 39, // right
18750         63232: 38, // up
18751         63233: 40, // down
18752         63276: 33, // page up
18753         63277: 34, // page down
18754         63272: 46, // delete
18755         63273: 36, // home
18756         63275: 35 // end
18757     },
18758     // normalize button clicks, don't see any way to feature detect this.
18759     btnMap: Ext.isIE ? {
18760         1: 0,
18761         4: 1,
18762         2: 2
18763     } : {
18764         0: 0,
18765         1: 1,
18766         2: 2
18767     },
18768
18769     constructor: function(event, freezeEvent){
18770         if (event) {
18771             this.setEvent(event.browserEvent || event, freezeEvent);
18772         }
18773     },
18774
18775     setEvent: function(event, freezeEvent){
18776         var me = this, button, options;
18777
18778         if (event == me || (event && event.browserEvent)) { // already wrapped
18779             return event;
18780         }
18781         me.browserEvent = event;
18782         if (event) {
18783             // normalize buttons
18784             button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
18785             if (me.clickRe.test(event.type) && button == -1) {
18786                 button = 0;
18787             }
18788             options = {
18789                 type: event.type,
18790                 button: button,
18791                 shiftKey: event.shiftKey,
18792                 // mac metaKey behaves like ctrlKey
18793                 ctrlKey: event.ctrlKey || event.metaKey || false,
18794                 altKey: event.altKey,
18795                 // in getKey these will be normalized for the mac
18796                 keyCode: event.keyCode,
18797                 charCode: event.charCode,
18798                 // cache the targets for the delayed and or buffered events
18799                 target: Ext.EventManager.getTarget(event),
18800                 relatedTarget: Ext.EventManager.getRelatedTarget(event),
18801                 currentTarget: event.currentTarget,
18802                 xy: (freezeEvent ? me.getXY() : null)
18803             };
18804         } else {
18805             options = {
18806                 button: -1,
18807                 shiftKey: false,
18808                 ctrlKey: false,
18809                 altKey: false,
18810                 keyCode: 0,
18811                 charCode: 0,
18812                 target: null,
18813                 xy: [0, 0]
18814             };
18815         }
18816         Ext.apply(me, options);
18817         return me;
18818     },
18819
18820     /**
18821      * Stop the event (preventDefault and stopPropagation)
18822      */
18823     stopEvent: function(){
18824         this.stopPropagation();
18825         this.preventDefault();
18826     },
18827
18828     /**
18829      * Prevents the browsers default handling of the event.
18830      */
18831     preventDefault: function(){
18832         if (this.browserEvent) {
18833             Ext.EventManager.preventDefault(this.browserEvent);
18834         }
18835     },
18836
18837     /**
18838      * Cancels bubbling of the event.
18839      */
18840     stopPropagation: function(){
18841         var browserEvent = this.browserEvent;
18842
18843         if (browserEvent) {
18844             if (browserEvent.type == 'mousedown') {
18845                 Ext.EventManager.stoppedMouseDownEvent.fire(this);
18846             }
18847             Ext.EventManager.stopPropagation(browserEvent);
18848         }
18849     },
18850
18851     /**
18852      * Gets the character code for the event.
18853      * @return {Number}
18854      */
18855     getCharCode: function(){
18856         return this.charCode || this.keyCode;
18857     },
18858
18859     /**
18860      * Returns a normalized keyCode for the event.
18861      * @return {Number} The key code
18862      */
18863     getKey: function(){
18864         return this.normalizeKey(this.keyCode || this.charCode);
18865     },
18866
18867     /**
18868      * Normalize key codes across browsers
18869      * @private
18870      * @param {Number} key The key code
18871      * @return {Number} The normalized code
18872      */
18873     normalizeKey: function(key){
18874         // can't feature detect this
18875         return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
18876     },
18877
18878     /**
18879      * Gets the x coordinate of the event.
18880      * @return {Number}
18881      * @deprecated 4.0 Replaced by {@link #getX}
18882      */
18883     getPageX: function(){
18884         return this.getX();
18885     },
18886
18887     /**
18888      * Gets the y coordinate of the event.
18889      * @return {Number}
18890      * @deprecated 4.0 Replaced by {@link #getY}
18891      */
18892     getPageY: function(){
18893         return this.getY();
18894     },
18895
18896     /**
18897      * Gets the x coordinate of the event.
18898      * @return {Number}
18899      */
18900     getX: function() {
18901         return this.getXY()[0];
18902     },
18903
18904     /**
18905      * Gets the y coordinate of the event.
18906      * @return {Number}
18907      */
18908     getY: function() {
18909         return this.getXY()[1];
18910     },
18911
18912     /**
18913      * Gets the page coordinates of the event.
18914      * @return {Number[]} The xy values like [x, y]
18915      */
18916     getXY: function() {
18917         if (!this.xy) {
18918             // same for XY
18919             this.xy = Ext.EventManager.getPageXY(this.browserEvent);
18920         }
18921         return this.xy;
18922     },
18923
18924     /**
18925      * Gets the target for the event.
18926      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18927      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18928      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18929      * @return {HTMLElement}
18930      */
18931     getTarget : function(selector, maxDepth, returnEl){
18932         if (selector) {
18933             return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
18934         }
18935         return returnEl ? Ext.get(this.target) : this.target;
18936     },
18937
18938     /**
18939      * Gets the related target.
18940      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18941      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18942      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18943      * @return {HTMLElement}
18944      */
18945     getRelatedTarget : function(selector, maxDepth, returnEl){
18946         if (selector) {
18947             return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
18948         }
18949         return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
18950     },
18951
18952     /**
18953      * Correctly scales a given wheel delta.
18954      * @param {Number} delta The delta value.
18955      */
18956     correctWheelDelta : function (delta) {
18957         var scale = this.WHEEL_SCALE,
18958             ret = Math.round(delta / scale);
18959
18960         if (!ret && delta) {
18961             ret = (delta < 0) ? -1 : 1; // don't allow non-zero deltas to go to zero!
18962         }
18963
18964         return ret;
18965     },
18966
18967     /**
18968      * Returns the mouse wheel deltas for this event.
18969      * @return {Object} An object with "x" and "y" properties holding the mouse wheel deltas.
18970      */
18971     getWheelDeltas : function () {
18972         var me = this,
18973             event = me.browserEvent,
18974             dx = 0, dy = 0; // the deltas
18975
18976         if (Ext.isDefined(event.wheelDeltaX)) { // WebKit has both dimensions
18977             dx = event.wheelDeltaX;
18978             dy = event.wheelDeltaY;
18979         } else if (event.wheelDelta) { // old WebKit and IE
18980             dy = event.wheelDelta;
18981         } else if (event.detail) { // Gecko
18982             dy = -event.detail; // gecko is backwards
18983
18984             // Gecko sometimes returns really big values if the user changes settings to
18985             // scroll a whole page per scroll
18986             if (dy > 100) {
18987                 dy = 3;
18988             } else if (dy < -100) {
18989                 dy = -3;
18990             }
18991
18992             // Firefox 3.1 adds an axis field to the event to indicate direction of
18993             // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
18994             if (Ext.isDefined(event.axis) && event.axis === event.HORIZONTAL_AXIS) {
18995                 dx = dy;
18996                 dy = 0;
18997             }
18998         }
18999
19000         return {
19001             x: me.correctWheelDelta(dx),
19002             y: me.correctWheelDelta(dy)
19003         };
19004     },
19005
19006     /**
19007      * Normalizes mouse wheel y-delta across browsers. To get x-delta information, use
19008      * {@link #getWheelDeltas} instead.
19009      * @return {Number} The mouse wheel y-delta
19010      */
19011     getWheelDelta : function(){
19012         var deltas = this.getWheelDeltas();
19013
19014         return deltas.y;
19015     },
19016
19017     /**
19018      * 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.
19019      * Example usage:<pre><code>
19020 // Handle click on any child of an element
19021 Ext.getBody().on('click', function(e){
19022     if(e.within('some-el')){
19023         alert('Clicked on a child of some-el!');
19024     }
19025 });
19026
19027 // Handle click directly on an element, ignoring clicks on child nodes
19028 Ext.getBody().on('click', function(e,t){
19029     if((t.id == 'some-el') && !e.within(t, true)){
19030         alert('Clicked directly on some-el!');
19031     }
19032 });
19033 </code></pre>
19034      * @param {String/HTMLElement/Ext.Element} el The id, DOM element or Ext.Element to check
19035      * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
19036      * @param {Boolean} allowEl (optional) true to also check if the passed element is the target or related target
19037      * @return {Boolean}
19038      */
19039     within : function(el, related, allowEl){
19040         if(el){
19041             var t = related ? this.getRelatedTarget() : this.getTarget(),
19042                 result;
19043
19044             if (t) {
19045                 result = Ext.fly(el).contains(t);
19046                 if (!result && allowEl) {
19047                     result = t == Ext.getDom(el);
19048                 }
19049                 return result;
19050             }
19051         }
19052         return false;
19053     },
19054
19055     /**
19056      * Checks if the key pressed was a "navigation" key
19057      * @return {Boolean} True if the press is a navigation keypress
19058      */
19059     isNavKeyPress : function(){
19060         var me = this,
19061             k = this.normalizeKey(me.keyCode);
19062
19063        return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
19064        k == me.RETURN ||
19065        k == me.TAB ||
19066        k == me.ESC;
19067     },
19068
19069     /**
19070      * Checks if the key pressed was a "special" key
19071      * @return {Boolean} True if the press is a special keypress
19072      */
19073     isSpecialKey : function(){
19074         var k = this.normalizeKey(this.keyCode);
19075         return (this.type == 'keypress' && this.ctrlKey) ||
19076         this.isNavKeyPress() ||
19077         (k == this.BACKSPACE) || // Backspace
19078         (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
19079         (k >= 44 && k <= 46);   // Print Screen, Insert, Delete
19080     },
19081
19082     /**
19083      * Returns a point object that consists of the object coordinates.
19084      * @return {Ext.util.Point} point
19085      */
19086     getPoint : function(){
19087         var xy = this.getXY();
19088         return Ext.create('Ext.util.Point', xy[0], xy[1]);
19089     },
19090
19091    /**
19092     * Returns true if the control, meta, shift or alt key was pressed during this event.
19093     * @return {Boolean}
19094     */
19095     hasModifier : function(){
19096         return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
19097     },
19098
19099     /**
19100      * Injects a DOM event using the data in this object and (optionally) a new target.
19101      * This is a low-level technique and not likely to be used by application code. The
19102      * currently supported event types are:
19103      * <p><b>HTMLEvents</b></p>
19104      * <ul>
19105      * <li>load</li>
19106      * <li>unload</li>
19107      * <li>select</li>
19108      * <li>change</li>
19109      * <li>submit</li>
19110      * <li>reset</li>
19111      * <li>resize</li>
19112      * <li>scroll</li>
19113      * </ul>
19114      * <p><b>MouseEvents</b></p>
19115      * <ul>
19116      * <li>click</li>
19117      * <li>dblclick</li>
19118      * <li>mousedown</li>
19119      * <li>mouseup</li>
19120      * <li>mouseover</li>
19121      * <li>mousemove</li>
19122      * <li>mouseout</li>
19123      * </ul>
19124      * <p><b>UIEvents</b></p>
19125      * <ul>
19126      * <li>focusin</li>
19127      * <li>focusout</li>
19128      * <li>activate</li>
19129      * <li>focus</li>
19130      * <li>blur</li>
19131      * </ul>
19132      * @param {Ext.Element/HTMLElement} target (optional) If specified, the target for the event. This
19133      * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
19134      * is used to determine the target.
19135      */
19136     injectEvent: function () {
19137         var API,
19138             dispatchers = {}; // keyed by event type (e.g., 'mousedown')
19139
19140         // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
19141
19142         // IE9 has createEvent, but this code causes major problems with htmleditor (it
19143         // blocks all mouse events and maybe more). TODO
19144
19145         if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
19146             API = {
19147                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
19148                     var event = doc.createEvent('HTMLEvents');
19149
19150                     event.initEvent(type, bubbles, cancelable);
19151                     return event;
19152                 },
19153
19154                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
19155                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
19156                                             button, relatedTarget) {
19157                     var event = doc.createEvent('MouseEvents'),
19158                         view = doc.defaultView || window;
19159
19160                     if (event.initMouseEvent) {
19161                         event.initMouseEvent(type, bubbles, cancelable, view, detail,
19162                                     clientX, clientY, clientX, clientY, ctrlKey, altKey,
19163                                     shiftKey, metaKey, button, relatedTarget);
19164                     } else { // old Safari
19165                         event = doc.createEvent('UIEvents');
19166                         event.initEvent(type, bubbles, cancelable);
19167                         event.view = view;
19168                         event.detail = detail;
19169                         event.screenX = clientX;
19170                         event.screenY = clientY;
19171                         event.clientX = clientX;
19172                         event.clientY = clientY;
19173                         event.ctrlKey = ctrlKey;
19174                         event.altKey = altKey;
19175                         event.metaKey = metaKey;
19176                         event.shiftKey = shiftKey;
19177                         event.button = button;
19178                         event.relatedTarget = relatedTarget;
19179                     }
19180
19181                     return event;
19182                 },
19183
19184                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
19185                     var event = doc.createEvent('UIEvents'),
19186                         view = doc.defaultView || window;
19187
19188                     event.initUIEvent(type, bubbles, cancelable, view, detail);
19189                     return event;
19190                 },
19191
19192                 fireEvent: function (target, type, event) {
19193                     target.dispatchEvent(event);
19194                 },
19195
19196                 fixTarget: function (target) {
19197                     // Safari3 doesn't have window.dispatchEvent()
19198                     if (target == window && !target.dispatchEvent) {
19199                         return document;
19200                     }
19201
19202                     return target;
19203                 }
19204             };
19205         } else if (document.createEventObject) { // else if (IE)
19206             var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
19207
19208             API = {
19209                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
19210                     var event = doc.createEventObject();
19211                     event.bubbles = bubbles;
19212                     event.cancelable = cancelable;
19213                     return event;
19214                 },
19215
19216                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
19217                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
19218                                             button, relatedTarget) {
19219                     var event = doc.createEventObject();
19220                     event.bubbles = bubbles;
19221                     event.cancelable = cancelable;
19222                     event.detail = detail;
19223                     event.screenX = clientX;
19224                     event.screenY = clientY;
19225                     event.clientX = clientX;
19226                     event.clientY = clientY;
19227                     event.ctrlKey = ctrlKey;
19228                     event.altKey = altKey;
19229                     event.shiftKey = shiftKey;
19230                     event.metaKey = metaKey;
19231                     event.button = crazyIEButtons[button] || button;
19232                     event.relatedTarget = relatedTarget; // cannot assign to/fromElement
19233                     return event;
19234                 },
19235
19236                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
19237                     var event = doc.createEventObject();
19238                     event.bubbles = bubbles;
19239                     event.cancelable = cancelable;
19240                     return event;
19241                 },
19242
19243                 fireEvent: function (target, type, event) {
19244                     target.fireEvent('on' + type, event);
19245                 },
19246
19247                 fixTarget: function (target) {
19248                     if (target == document) {
19249                         // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
19250                         // IE6,IE7 cannot properly call document.fireEvent()
19251                         return document.documentElement;
19252                     }
19253
19254                     return target;
19255                 }
19256             };
19257         }
19258
19259         //----------------
19260         // HTMLEvents
19261
19262         Ext.Object.each({
19263                 load:   [false, false],
19264                 unload: [false, false],
19265                 select: [true, false],
19266                 change: [true, false],
19267                 submit: [true, true],
19268                 reset:  [true, false],
19269                 resize: [true, false],
19270                 scroll: [true, false]
19271             },
19272             function (name, value) {
19273                 var bubbles = value[0], cancelable = value[1];
19274                 dispatchers[name] = function (targetEl, srcEvent) {
19275                     var e = API.createHtmlEvent(name, bubbles, cancelable);
19276                     API.fireEvent(targetEl, name, e);
19277                 };
19278             });
19279
19280         //----------------
19281         // MouseEvents
19282
19283         function createMouseEventDispatcher (type, detail) {
19284             var cancelable = (type != 'mousemove');
19285             return function (targetEl, srcEvent) {
19286                 var xy = srcEvent.getXY(),
19287                     e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
19288                                 detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
19289                                 srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
19290                                 srcEvent.relatedTarget);
19291                 API.fireEvent(targetEl, type, e);
19292             };
19293         }
19294
19295         Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
19296             function (eventName) {
19297                 dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
19298             });
19299
19300         //----------------
19301         // UIEvents
19302
19303         Ext.Object.each({
19304                 focusin:  [true, false],
19305                 focusout: [true, false],
19306                 activate: [true, true],
19307                 focus:    [false, false],
19308                 blur:     [false, false]
19309             },
19310             function (name, value) {
19311                 var bubbles = value[0], cancelable = value[1];
19312                 dispatchers[name] = function (targetEl, srcEvent) {
19313                     var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
19314                     API.fireEvent(targetEl, name, e);
19315                 };
19316             });
19317
19318         //---------
19319         if (!API) {
19320             // not even sure what ancient browsers fall into this category...
19321
19322             dispatchers = {}; // never mind all those we just built :P
19323
19324             API = {
19325                 fixTarget: function (t) {
19326                     return t;
19327                 }
19328             };
19329         }
19330
19331         function cannotInject (target, srcEvent) {
19332             // TODO log something
19333         }
19334
19335         return function (target) {
19336             var me = this,
19337                 dispatcher = dispatchers[me.type] || cannotInject,
19338                 t = target ? (target.dom || target) : me.getTarget();
19339
19340             t = API.fixTarget(t);
19341             dispatcher(t, me);
19342         };
19343     }() // call to produce method
19344
19345 }, function() {
19346
19347 Ext.EventObject = new Ext.EventObjectImpl();
19348
19349 });
19350
19351
19352 /**
19353  * @class Ext.Element
19354  */
19355 (function(){
19356     var doc = document,
19357         activeElement = null,
19358         isCSS1 = doc.compatMode == "CSS1Compat",
19359         ELEMENT = Ext.Element,
19360         fly = function(el){
19361             if (!_fly) {
19362                 _fly = new Ext.Element.Flyweight();
19363             }
19364             _fly.dom = el;
19365             return _fly;
19366         }, _fly;
19367
19368     // If the browser does not support document.activeElement we need some assistance.
19369     // This covers old Safari 3.2 (4.0 added activeElement along with just about all
19370     // other browsers). We need this support to handle issues with old Safari.
19371     if (!('activeElement' in doc) && doc.addEventListener) {
19372         doc.addEventListener('focus',
19373             function (ev) {
19374                 if (ev && ev.target) {
19375                     activeElement = (ev.target == doc) ? null : ev.target;
19376                 }
19377             }, true);
19378     }
19379
19380     /*
19381      * Helper function to create the function that will restore the selection.
19382      */
19383     function makeSelectionRestoreFn (activeEl, start, end) {
19384         return function () {
19385             activeEl.selectionStart = start;
19386             activeEl.selectionEnd = end;
19387         };
19388     }
19389
19390     Ext.apply(ELEMENT, {
19391         isAncestor : function(p, c) {
19392             var ret = false;
19393
19394             p = Ext.getDom(p);
19395             c = Ext.getDom(c);
19396             if (p && c) {
19397                 if (p.contains) {
19398                     return p.contains(c);
19399                 } else if (p.compareDocumentPosition) {
19400                     return !!(p.compareDocumentPosition(c) & 16);
19401                 } else {
19402                     while ((c = c.parentNode)) {
19403                         ret = c == p || ret;
19404                     }
19405                 }
19406             }
19407             return ret;
19408         },
19409
19410         /**
19411          * Returns the active element in the DOM. If the browser supports activeElement
19412          * on the document, this is returned. If not, the focus is tracked and the active
19413          * element is maintained internally.
19414          * @return {HTMLElement} The active (focused) element in the document.
19415          */
19416         getActiveElement: function () {
19417             return doc.activeElement || activeElement;
19418         },
19419
19420         /**
19421          * Creates a function to call to clean up problems with the work-around for the
19422          * WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to
19423          * the element before calling getComputedStyle and then to restore its original
19424          * display value. The problem with this is that it corrupts the selection of an
19425          * INPUT or TEXTAREA element (as in the "I-beam" goes away but ths focus remains).
19426          * To cleanup after this, we need to capture the selection of any such element and
19427          * then restore it after we have restored the display style.
19428          *
19429          * @param target {Element} The top-most element being adjusted.
19430          * @private
19431          */
19432         getRightMarginFixCleaner: function (target) {
19433             var supports = Ext.supports,
19434                 hasInputBug = supports.DisplayChangeInputSelectionBug,
19435                 hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug;
19436
19437             if (hasInputBug || hasTextAreaBug) {
19438                 var activeEl = doc.activeElement || activeElement, // save a call
19439                     tag = activeEl && activeEl.tagName,
19440                     start,
19441                     end;
19442
19443                 if ((hasTextAreaBug && tag == 'TEXTAREA') ||
19444                     (hasInputBug && tag == 'INPUT' && activeEl.type == 'text')) {
19445                     if (ELEMENT.isAncestor(target, activeEl)) {
19446                         start = activeEl.selectionStart;
19447                         end = activeEl.selectionEnd;
19448
19449                         if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe...
19450                             // We don't create the raw closure here inline because that
19451                             // will be costly even if we don't want to return it (nested
19452                             // function decls and exprs are often instantiated on entry
19453                             // regardless of whether execution ever reaches them):
19454                             return makeSelectionRestoreFn(activeEl, start, end);
19455                         }
19456                     }
19457                 }
19458             }
19459
19460             return Ext.emptyFn; // avoid special cases, just return a nop
19461         },
19462
19463         getViewWidth : function(full) {
19464             return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
19465         },
19466
19467         getViewHeight : function(full) {
19468             return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
19469         },
19470
19471         getDocumentHeight: function() {
19472             return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
19473         },
19474
19475         getDocumentWidth: function() {
19476             return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
19477         },
19478
19479         getViewportHeight: function(){
19480             return Ext.isIE ?
19481                    (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
19482                    self.innerHeight;
19483         },
19484
19485         getViewportWidth : function() {
19486             return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
19487                    Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
19488         },
19489
19490         getY : function(el) {
19491             return ELEMENT.getXY(el)[1];
19492         },
19493
19494         getX : function(el) {
19495             return ELEMENT.getXY(el)[0];
19496         },
19497
19498         getOffsetParent: function (el) {
19499             el = Ext.getDom(el);
19500             try {
19501                 // accessing offsetParent can throw "Unspecified Error" in IE6-8 (not 9)
19502                 return el.offsetParent;
19503             } catch (e) {
19504                 var body = document.body; // safe bet, unless...
19505                 return (el == body) ? null : body;
19506             }
19507         },
19508
19509         getXY : function(el) {
19510             var p,
19511                 pe,
19512                 b,
19513                 bt,
19514                 bl,
19515                 dbd,
19516                 x = 0,
19517                 y = 0,
19518                 scroll,
19519                 hasAbsolute,
19520                 bd = (doc.body || doc.documentElement),
19521                 ret;
19522
19523             el = Ext.getDom(el);
19524
19525             if(el != bd){
19526                 hasAbsolute = fly(el).isStyle("position", "absolute");
19527
19528                 if (el.getBoundingClientRect) {
19529                     try {
19530                         b = el.getBoundingClientRect();
19531                         scroll = fly(document).getScroll();
19532                         ret = [ Math.round(b.left + scroll.left), Math.round(b.top + scroll.top) ];
19533                     } catch (e) {
19534                         // IE6-8 can also throw from getBoundingClientRect...
19535                     }
19536                 }
19537
19538                 if (!ret) {
19539                     for (p = el; p; p = ELEMENT.getOffsetParent(p)) {
19540                         pe = fly(p);
19541                         x += p.offsetLeft;
19542                         y += p.offsetTop;
19543
19544                         hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
19545
19546                         if (Ext.isGecko) {
19547                             y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
19548                             x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
19549
19550                             if (p != el && !pe.isStyle('overflow','visible')) {
19551                                 x += bl;
19552                                 y += bt;
19553                             }
19554                         }
19555                     }
19556
19557                     if (Ext.isSafari && hasAbsolute) {
19558                         x -= bd.offsetLeft;
19559                         y -= bd.offsetTop;
19560                     }
19561
19562                     if (Ext.isGecko && !hasAbsolute) {
19563                         dbd = fly(bd);
19564                         x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
19565                         y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
19566                     }
19567
19568                     p = el.parentNode;
19569                     while (p && p != bd) {
19570                         if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
19571                             x -= p.scrollLeft;
19572                             y -= p.scrollTop;
19573                         }
19574                         p = p.parentNode;
19575                     }
19576                     ret = [x,y];
19577                 }
19578             }
19579             return ret || [0,0];
19580         },
19581
19582         setXY : function(el, xy) {
19583             (el = Ext.fly(el, '_setXY')).position();
19584
19585             var pts = el.translatePoints(xy),
19586                 style = el.dom.style,
19587                 pos;
19588
19589             for (pos in pts) {
19590                 if (!isNaN(pts[pos])) {
19591                     style[pos] = pts[pos] + "px";
19592                 }
19593             }
19594         },
19595
19596         setX : function(el, x) {
19597             ELEMENT.setXY(el, [x, false]);
19598         },
19599
19600         setY : function(el, y) {
19601             ELEMENT.setXY(el, [false, y]);
19602         },
19603
19604         /**
19605          * Serializes a DOM form into a url encoded string
19606          * @param {Object} form The form
19607          * @return {String} The url encoded form
19608          */
19609         serializeForm: function(form) {
19610             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
19611                 hasSubmit = false,
19612                 encoder = encodeURIComponent,
19613                 name,
19614                 data = '',
19615                 type,
19616                 hasValue;
19617
19618             Ext.each(fElements, function(element){
19619                 name = element.name;
19620                 type = element.type;
19621
19622                 if (!element.disabled && name) {
19623                     if (/select-(one|multiple)/i.test(type)) {
19624                         Ext.each(element.options, function(opt){
19625                             if (opt.selected) {
19626                                 hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
19627                                 data += Ext.String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
19628                             }
19629                         });
19630                     } else if (!(/file|undefined|reset|button/i.test(type))) {
19631                         if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
19632                             data += encoder(name) + '=' + encoder(element.value) + '&';
19633                             hasSubmit = /submit/i.test(type);
19634                         }
19635                     }
19636                 }
19637             });
19638             return data.substr(0, data.length - 1);
19639         }
19640     });
19641 })();
19642
19643 /**
19644  * @class Ext.Element
19645  */
19646
19647 Ext.Element.addMethods((function(){
19648     var focusRe = /button|input|textarea|select|object/;
19649     return {
19650         /**
19651          * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
19652          * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
19653          * back in, the function is not called.
19654          * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
19655          * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
19656          * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
19657          * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:<pre><code>
19658 // Hide the menu if the mouse moves out for 250ms or more
19659 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
19660
19661 ...
19662 // Remove mouseleave monitor on menu destroy
19663 this.menuEl.un(this.mouseLeaveMonitor);
19664     </code></pre>
19665          */
19666         monitorMouseLeave: function(delay, handler, scope) {
19667             var me = this,
19668                 timer,
19669                 listeners = {
19670                     mouseleave: function(e) {
19671                         timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
19672                     },
19673                     mouseenter: function() {
19674                         clearTimeout(timer);
19675                     },
19676                     freezeEvent: true
19677                 };
19678
19679             me.on(listeners);
19680             return listeners;
19681         },
19682
19683         /**
19684          * Stops the specified event(s) from bubbling and optionally prevents the default action
19685          * @param {String/String[]} eventName an event / array of events to stop from bubbling
19686          * @param {Boolean} preventDefault (optional) true to prevent the default action too
19687          * @return {Ext.Element} this
19688          */
19689         swallowEvent : function(eventName, preventDefault) {
19690             var me = this;
19691             function fn(e) {
19692                 e.stopPropagation();
19693                 if (preventDefault) {
19694                     e.preventDefault();
19695                 }
19696             }
19697
19698             if (Ext.isArray(eventName)) {
19699                 Ext.each(eventName, function(e) {
19700                      me.on(e, fn);
19701                 });
19702                 return me;
19703             }
19704             me.on(eventName, fn);
19705             return me;
19706         },
19707
19708         /**
19709          * Create an event handler on this element such that when the event fires and is handled by this element,
19710          * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
19711          * @param {String} eventName The type of event to relay
19712          * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
19713          * for firing the relayed event
19714          */
19715         relayEvent : function(eventName, observable) {
19716             this.on(eventName, function(e) {
19717                 observable.fireEvent(eventName, e);
19718             });
19719         },
19720
19721         /**
19722          * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
19723          * @param {Boolean} forceReclean (optional) By default the element
19724          * keeps track if it has been cleaned already so
19725          * you can call this over and over. However, if you update the element and
19726          * need to force a reclean, you can pass true.
19727          */
19728         clean : function(forceReclean) {
19729             var me  = this,
19730                 dom = me.dom,
19731                 n   = dom.firstChild,
19732                 nx,
19733                 ni  = -1;
19734     
19735             if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
19736                 return me;
19737             }
19738
19739             while (n) {
19740                 nx = n.nextSibling;
19741                 if (n.nodeType == 3) {
19742                     // Remove empty/whitespace text nodes
19743                     if (!(/\S/.test(n.nodeValue))) {
19744                         dom.removeChild(n);
19745                     // Combine adjacent text nodes
19746                     } else if (nx && nx.nodeType == 3) {
19747                         n.appendData(Ext.String.trim(nx.data));
19748                         dom.removeChild(nx);
19749                         nx = n.nextSibling;
19750                         n.nodeIndex = ++ni;
19751                     }
19752                 } else {
19753                     // Recursively clean
19754                     Ext.fly(n).clean();
19755                     n.nodeIndex = ++ni;
19756                 }
19757                 n = nx;
19758             }
19759
19760             Ext.Element.data(dom, 'isCleaned', true);
19761             return me;
19762         },
19763
19764         /**
19765          * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
19766          * parameter as {@link Ext.ElementLoader#load}
19767          * @return {Ext.Element} this
19768          */
19769         load : function(options) {
19770             this.getLoader().load(options);
19771             return this;
19772         },
19773
19774         /**
19775         * Gets this element's {@link Ext.ElementLoader ElementLoader}
19776         * @return {Ext.ElementLoader} The loader
19777         */
19778         getLoader : function() {
19779             var dom = this.dom,
19780                 data = Ext.Element.data,
19781                 loader = data(dom, 'loader');
19782     
19783             if (!loader) {
19784                 loader = Ext.create('Ext.ElementLoader', {
19785                     target: this
19786                 });
19787                 data(dom, 'loader', loader);
19788             }
19789             return loader;
19790         },
19791
19792         /**
19793         * Update the innerHTML of this element, optionally searching for and processing scripts
19794         * @param {String} html The new HTML
19795         * @param {Boolean} [loadScripts=false] True to look for and process scripts
19796         * @param {Function} [callback] For async script loading you can be notified when the update completes
19797         * @return {Ext.Element} this
19798          */
19799         update : function(html, loadScripts, callback) {
19800             var me = this,
19801                 id,
19802                 dom,
19803                 interval;
19804
19805             if (!me.dom) {
19806                 return me;
19807             }
19808             html = html || '';
19809             dom = me.dom;
19810
19811             if (loadScripts !== true) {
19812                 dom.innerHTML = html;
19813                 Ext.callback(callback, me);
19814                 return me;
19815             }
19816
19817             id  = Ext.id();
19818             html += '<span id="' + id + '"></span>';
19819
19820             interval = setInterval(function(){
19821                 if (!document.getElementById(id)) {
19822                     return false;
19823                 }
19824                 clearInterval(interval);
19825                 var DOC    = document,
19826                     hd     = DOC.getElementsByTagName("head")[0],
19827                     re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
19828                     srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
19829                     typeRe = /\stype=([\'\"])(.*?)\1/i,
19830                     match,
19831                     attrs,
19832                     srcMatch,
19833                     typeMatch,
19834                     el,
19835                     s;
19836
19837                 while ((match = re.exec(html))) {
19838                     attrs = match[1];
19839                     srcMatch = attrs ? attrs.match(srcRe) : false;
19840                     if (srcMatch && srcMatch[2]) {
19841                        s = DOC.createElement("script");
19842                        s.src = srcMatch[2];
19843                        typeMatch = attrs.match(typeRe);
19844                        if (typeMatch && typeMatch[2]) {
19845                            s.type = typeMatch[2];
19846                        }
19847                        hd.appendChild(s);
19848                     } else if (match[2] && match[2].length > 0) {
19849                         if (window.execScript) {
19850                            window.execScript(match[2]);
19851                         } else {
19852                            window.eval(match[2]);
19853                         }
19854                     }
19855                 }
19856
19857                 el = DOC.getElementById(id);
19858                 if (el) {
19859                     Ext.removeNode(el);
19860                 }
19861                 Ext.callback(callback, me);
19862             }, 20);
19863             dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
19864             return me;
19865         },
19866
19867         // inherit docs, overridden so we can add removeAnchor
19868         removeAllListeners : function() {
19869             this.removeAnchor();
19870             Ext.EventManager.removeAll(this.dom);
19871             return this;
19872         },
19873     
19874         /**
19875          * Gets the parent node of the current element taking into account Ext.scopeResetCSS
19876          * @protected
19877          * @return {HTMLElement} The parent element
19878          */
19879         getScopeParent: function(){
19880             var parent = this.dom.parentNode;
19881             return Ext.scopeResetCSS ? parent.parentNode : parent;
19882         },
19883
19884         /**
19885          * Creates a proxy element of this element
19886          * @param {String/Object} config The class name of the proxy element or a DomHelper config object
19887          * @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to (defaults to document.body)
19888          * @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
19889          * @return {Ext.Element} The new proxy element
19890          */
19891         createProxy : function(config, renderTo, matchBox) {
19892             config = (typeof config == 'object') ? config : {tag : "div", cls: config};
19893
19894             var me = this,
19895                 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
19896                                    Ext.DomHelper.insertBefore(me.dom, config, true);
19897
19898             proxy.setVisibilityMode(Ext.Element.DISPLAY);
19899             proxy.hide();
19900             if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
19901                proxy.setBox(me.getBox());
19902             }
19903             return proxy;
19904         },
19905     
19906         /**
19907          * Checks whether this element can be focused.
19908          * @return {Boolean} True if the element is focusable
19909          */
19910         focusable: function(){
19911             var dom = this.dom,
19912                 nodeName = dom.nodeName.toLowerCase(),
19913                 canFocus = false,
19914                 hasTabIndex = !isNaN(dom.tabIndex);
19915             
19916             if (!dom.disabled) {
19917                 if (focusRe.test(nodeName)) {
19918                     canFocus = true;
19919                 } else {
19920                     canFocus = nodeName == 'a' ? dom.href || hasTabIndex : hasTabIndex;
19921                 }
19922             }
19923             return canFocus && this.isVisible(true);
19924         }    
19925     };
19926 })());
19927 Ext.Element.prototype.clearListeners = Ext.Element.prototype.removeAllListeners;
19928
19929 /**
19930  * @class Ext.Element
19931  */
19932 Ext.Element.addMethods({
19933     /**
19934      * Gets the x,y coordinates specified by the anchor position on the element.
19935      * @param {String} [anchor='c'] The specified anchor position.  See {@link #alignTo}
19936      * for details on supported anchor positions.
19937      * @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
19938      * of page coordinates
19939      * @param {Object} [size] An object containing the size to use for calculating anchor position
19940      * {width: (target width), height: (target height)} (defaults to the element's current size)
19941      * @return {Number[]} [x, y] An array containing the element's x and y coordinates
19942      */
19943     getAnchorXY : function(anchor, local, s){
19944         //Passing a different size is useful for pre-calculating anchors,
19945         //especially for anchored animations that change the el size.
19946         anchor = (anchor || "tl").toLowerCase();
19947         s = s || {};
19948
19949         var me = this,
19950             vp = me.dom == document.body || me.dom == document,
19951             w = s.width || vp ? Ext.Element.getViewWidth() : me.getWidth(),
19952             h = s.height || vp ? Ext.Element.getViewHeight() : me.getHeight(),
19953             xy,
19954             r = Math.round,
19955             o = me.getXY(),
19956             scroll = me.getScroll(),
19957             extraX = vp ? scroll.left : !local ? o[0] : 0,
19958             extraY = vp ? scroll.top : !local ? o[1] : 0,
19959             hash = {
19960                 c  : [r(w * 0.5), r(h * 0.5)],
19961                 t  : [r(w * 0.5), 0],
19962                 l  : [0, r(h * 0.5)],
19963                 r  : [w, r(h * 0.5)],
19964                 b  : [r(w * 0.5), h],
19965                 tl : [0, 0],
19966                 bl : [0, h],
19967                 br : [w, h],
19968                 tr : [w, 0]
19969             };
19970
19971         xy = hash[anchor];
19972         return [xy[0] + extraX, xy[1] + extraY];
19973     },
19974
19975     /**
19976      * Anchors an element to another element and realigns it when the window is resized.
19977      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19978      * @param {String} position The position to align to.
19979      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19980      * @param {Boolean/Object} [animate] True for the default animation or a standard Element animation config object
19981      * @param {Boolean/Number} [monitorScroll] True to monitor body scroll and reposition. If this parameter
19982      * is a number, it is used as the buffer delay (defaults to 50ms).
19983      * @param {Function} [callback] The function to call after the animation finishes
19984      * @return {Ext.Element} this
19985      */
19986     anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
19987         var me = this,
19988             dom = me.dom,
19989             scroll = !Ext.isEmpty(monitorScroll),
19990             action = function(){
19991                 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
19992                 Ext.callback(callback, Ext.fly(dom));
19993             },
19994             anchor = this.getAnchor();
19995
19996         // previous listener anchor, remove it
19997         this.removeAnchor();
19998         Ext.apply(anchor, {
19999             fn: action,
20000             scroll: scroll
20001         });
20002
20003         Ext.EventManager.onWindowResize(action, null);
20004
20005         if(scroll){
20006             Ext.EventManager.on(window, 'scroll', action, null,
20007                 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
20008         }
20009         action.call(me); // align immediately
20010         return me;
20011     },
20012
20013     /**
20014      * Remove any anchor to this element. See {@link #anchorTo}.
20015      * @return {Ext.Element} this
20016      */
20017     removeAnchor : function(){
20018         var me = this,
20019             anchor = this.getAnchor();
20020
20021         if(anchor && anchor.fn){
20022             Ext.EventManager.removeResizeListener(anchor.fn);
20023             if(anchor.scroll){
20024                 Ext.EventManager.un(window, 'scroll', anchor.fn);
20025             }
20026             delete anchor.fn;
20027         }
20028         return me;
20029     },
20030
20031     // private
20032     getAnchor : function(){
20033         var data = Ext.Element.data,
20034             dom = this.dom;
20035             if (!dom) {
20036                 return;
20037             }
20038             var anchor = data(dom, '_anchor');
20039
20040         if(!anchor){
20041             anchor = data(dom, '_anchor', {});
20042         }
20043         return anchor;
20044     },
20045
20046     getAlignVector: function(el, spec, offset) {
20047         var me = this,
20048             side = {t:"top", l:"left", r:"right", b: "bottom"},
20049             thisRegion = me.getRegion(),
20050             elRegion;
20051
20052         el = Ext.get(el);
20053         if(!el || !el.dom){
20054             Ext.Error.raise({
20055                 sourceClass: 'Ext.Element',
20056                 sourceMethod: 'getAlignVector',
20057                 msg: 'Attempted to align an element that doesn\'t exist'
20058             });
20059         }
20060
20061         elRegion = el.getRegion();
20062     },
20063
20064     /**
20065      * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
20066      * supported position values.
20067      * @param {String/HTMLElement/Ext.Element} element The element to align to.
20068      * @param {String} [position="tl-bl?"] The position to align to (defaults to )
20069      * @param {Number[]} [offsets] Offset the positioning by [x, y]
20070      * @return {Number[]} [x, y]
20071      */
20072     getAlignToXY : function(el, p, o){
20073         el = Ext.get(el);
20074
20075         if(!el || !el.dom){
20076             Ext.Error.raise({
20077                 sourceClass: 'Ext.Element',
20078                 sourceMethod: 'getAlignToXY',
20079                 msg: 'Attempted to align an element that doesn\'t exist'
20080             });
20081         }
20082
20083         o = o || [0,0];
20084         p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
20085
20086         var me = this,
20087             d = me.dom,
20088             a1,
20089             a2,
20090             x,
20091             y,
20092             //constrain the aligned el to viewport if necessary
20093             w,
20094             h,
20095             r,
20096             dw = Ext.Element.getViewWidth() -10, // 10px of margin for ie
20097             dh = Ext.Element.getViewHeight()-10, // 10px of margin for ie
20098             p1y,
20099             p1x,
20100             p2y,
20101             p2x,
20102             swapY,
20103             swapX,
20104             doc = document,
20105             docElement = doc.documentElement,
20106             docBody = doc.body,
20107             scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
20108             scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
20109             c = false, //constrain to viewport
20110             p1 = "",
20111             p2 = "",
20112             m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
20113
20114         if(!m){
20115             Ext.Error.raise({
20116                 sourceClass: 'Ext.Element',
20117                 sourceMethod: 'getAlignToXY',
20118                 el: el,
20119                 position: p,
20120                 offset: o,
20121                 msg: 'Attemmpted to align an element with an invalid position: "' + p + '"'
20122             });
20123         }
20124
20125         p1 = m[1];
20126         p2 = m[2];
20127         c = !!m[3];
20128
20129         //Subtract the aligned el's internal xy from the target's offset xy
20130         //plus custom offset to get the aligned el's new offset xy
20131         a1 = me.getAnchorXY(p1, true);
20132         a2 = el.getAnchorXY(p2, false);
20133
20134         x = a2[0] - a1[0] + o[0];
20135         y = a2[1] - a1[1] + o[1];
20136
20137         if(c){
20138            w = me.getWidth();
20139            h = me.getHeight();
20140            r = el.getRegion();
20141            //If we are at a viewport boundary and the aligned el is anchored on a target border that is
20142            //perpendicular to the vp border, allow the aligned el to slide on that border,
20143            //otherwise swap the aligned el to the opposite border of the target.
20144            p1y = p1.charAt(0);
20145            p1x = p1.charAt(p1.length-1);
20146            p2y = p2.charAt(0);
20147            p2x = p2.charAt(p2.length-1);
20148            swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
20149            swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
20150
20151
20152            if (x + w > dw + scrollX) {
20153                 x = swapX ? r.left-w : dw+scrollX-w;
20154            }
20155            if (x < scrollX) {
20156                x = swapX ? r.right : scrollX;
20157            }
20158            if (y + h > dh + scrollY) {
20159                 y = swapY ? r.top-h : dh+scrollY-h;
20160             }
20161            if (y < scrollY){
20162                y = swapY ? r.bottom : scrollY;
20163            }
20164         }
20165         return [x,y];
20166     },
20167
20168     /**
20169      * Aligns this element with another element relative to the specified anchor points. If the other element is the
20170      * document it aligns it to the viewport.
20171      * The position parameter is optional, and can be specified in any one of the following formats:
20172      * <ul>
20173      *   <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
20174      *   <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
20175      *       The element being aligned will position its top-left corner (tl) to that point.  <i>This method has been
20176      *       deprecated in favor of the newer two anchor syntax below</i>.</li>
20177      *   <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
20178      *       element's anchor point, and the second value is used as the target's anchor point.</li>
20179      * </ul>
20180      * In addition to the anchor points, the position parameter also supports the "?" character.  If "?" is passed at the end of
20181      * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
20182      * the viewport if necessary.  Note that the element being aligned might be swapped to align to a different position than
20183      * that specified in order to enforce the viewport constraints.
20184      * Following are all of the supported anchor positions:
20185 <pre>
20186 Value  Description
20187 -----  -----------------------------
20188 tl     The top left corner (default)
20189 t      The center of the top edge
20190 tr     The top right corner
20191 l      The center of the left edge
20192 c      In the center of the element
20193 r      The center of the right edge
20194 bl     The bottom left corner
20195 b      The center of the bottom edge
20196 br     The bottom right corner
20197 </pre>
20198 Example Usage:
20199 <pre><code>
20200 // align el to other-el using the default positioning ("tl-bl", non-constrained)
20201 el.alignTo("other-el");
20202
20203 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
20204 el.alignTo("other-el", "tr?");
20205
20206 // align the bottom right corner of el with the center left edge of other-el
20207 el.alignTo("other-el", "br-l?");
20208
20209 // align the center of el with the bottom left corner of other-el and
20210 // adjust the x position by -6 pixels (and the y position by 0)
20211 el.alignTo("other-el", "c-bl", [-6, 0]);
20212 </code></pre>
20213      * @param {String/HTMLElement/Ext.Element} element The element to align to.
20214      * @param {String} [position="tl-bl?"] The position to align to
20215      * @param {Number[]} [offsets] Offset the positioning by [x, y]
20216      * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
20217      * @return {Ext.Element} this
20218      */
20219     alignTo : function(element, position, offsets, animate){
20220         var me = this;
20221         return me.setXY(me.getAlignToXY(element, position, offsets),
20222                         me.anim && !!animate ? me.anim(animate) : false);
20223     },
20224
20225     // private ==>  used outside of core
20226     adjustForConstraints : function(xy, parent) {
20227         var vector = this.getConstrainVector(parent, xy);
20228         if (vector) {
20229             xy[0] += vector[0];
20230             xy[1] += vector[1];
20231         }
20232         return xy;
20233     },
20234
20235     /**
20236      * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
20237      * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
20238      * <p>Priority is given to constraining the top and left within the constraint.</p>
20239      * <p>The constraint may either be an existing element into which this element is to be constrained, or
20240      * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
20241      * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
20242      * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
20243      * of using this Element's current position;
20244      * @returns {Number[]/Boolean} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
20245      * vector by which this element must be translated. Otherwise, <code>false</code>.
20246      */
20247     getConstrainVector: function(constrainTo, proposedPosition) {
20248         if (!(constrainTo instanceof Ext.util.Region)) {
20249             constrainTo = Ext.get(constrainTo).getViewRegion();
20250         }
20251         var thisRegion = this.getRegion(),
20252             vector = [0, 0],
20253             shadowSize = this.shadow && this.shadow.offset,
20254             overflowed = false;
20255
20256         // Shift this region to occupy the proposed position
20257         if (proposedPosition) {
20258             thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
20259         }
20260
20261         // Reduce the constrain region to allow for shadow
20262         // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
20263         if (shadowSize) {
20264             constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
20265         }
20266
20267         // Constrain the X coordinate by however much this Element overflows
20268         if (thisRegion.right > constrainTo.right) {
20269             overflowed = true;
20270             vector[0] = (constrainTo.right - thisRegion.right);    // overflowed the right
20271         }
20272         if (thisRegion.left + vector[0] < constrainTo.left) {
20273             overflowed = true;
20274             vector[0] = (constrainTo.left - thisRegion.left);      // overflowed the left
20275         }
20276
20277         // Constrain the Y coordinate by however much this Element overflows
20278         if (thisRegion.bottom > constrainTo.bottom) {
20279             overflowed = true;
20280             vector[1] = (constrainTo.bottom - thisRegion.bottom);  // overflowed the bottom
20281         }
20282         if (thisRegion.top + vector[1] < constrainTo.top) {
20283             overflowed = true;
20284             vector[1] = (constrainTo.top - thisRegion.top);        // overflowed the top
20285         }
20286         return overflowed ? vector : false;
20287     },
20288
20289     /**
20290     * Calculates the x, y to center this element on the screen
20291     * @return {Number[]} The x, y values [x, y]
20292     */
20293     getCenterXY : function(){
20294         return this.getAlignToXY(document, 'c-c');
20295     },
20296
20297     /**
20298     * Centers the Element in either the viewport, or another Element.
20299     * @param {String/HTMLElement/Ext.Element} centerIn (optional) The element in which to center the element.
20300     */
20301     center : function(centerIn){
20302         return this.alignTo(centerIn || document, 'c-c');
20303     }
20304 });
20305
20306 /**
20307  * @class Ext.Element
20308  */
20309 (function(){
20310
20311 var ELEMENT = Ext.Element,
20312     LEFT = "left",
20313     RIGHT = "right",
20314     TOP = "top",
20315     BOTTOM = "bottom",
20316     POSITION = "position",
20317     STATIC = "static",
20318     RELATIVE = "relative",
20319     AUTO = "auto",
20320     ZINDEX = "z-index";
20321
20322 Ext.override(Ext.Element, {
20323     /**
20324       * 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).
20325       * @return {Number} The X position of the element
20326       */
20327     getX : function(){
20328         return ELEMENT.getX(this.dom);
20329     },
20330
20331     /**
20332       * 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).
20333       * @return {Number} The Y position of the element
20334       */
20335     getY : function(){
20336         return ELEMENT.getY(this.dom);
20337     },
20338
20339     /**
20340       * 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).
20341       * @return {Number[]} The XY position of the element
20342       */
20343     getXY : function(){
20344         return ELEMENT.getXY(this.dom);
20345     },
20346
20347     /**
20348       * 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.
20349       * @param {String/HTMLElement/Ext.Element} element The element to get the offsets from.
20350       * @return {Number[]} The XY page offsets (e.g. [100, -200])
20351       */
20352     getOffsetsTo : function(el){
20353         var o = this.getXY(),
20354             e = Ext.fly(el, '_internal').getXY();
20355         return [o[0]-e[0],o[1]-e[1]];
20356     },
20357
20358     /**
20359      * 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).
20360      * @param {Number} The X position of the element
20361      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20362      * @return {Ext.Element} this
20363      */
20364     setX : function(x, animate){
20365         return this.setXY([x, this.getY()], animate);
20366     },
20367
20368     /**
20369      * 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).
20370      * @param {Number} The Y position of the element
20371      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20372      * @return {Ext.Element} this
20373      */
20374     setY : function(y, animate){
20375         return this.setXY([this.getX(), y], animate);
20376     },
20377
20378     /**
20379      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
20380      * @param {String} left The left CSS property value
20381      * @return {Ext.Element} this
20382      */
20383     setLeft : function(left){
20384         this.setStyle(LEFT, this.addUnits(left));
20385         return this;
20386     },
20387
20388     /**
20389      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
20390      * @param {String} top The top CSS property value
20391      * @return {Ext.Element} this
20392      */
20393     setTop : function(top){
20394         this.setStyle(TOP, this.addUnits(top));
20395         return this;
20396     },
20397
20398     /**
20399      * Sets the element's CSS right style.
20400      * @param {String} right The right CSS property value
20401      * @return {Ext.Element} this
20402      */
20403     setRight : function(right){
20404         this.setStyle(RIGHT, this.addUnits(right));
20405         return this;
20406     },
20407
20408     /**
20409      * Sets the element's CSS bottom style.
20410      * @param {String} bottom The bottom CSS property value
20411      * @return {Ext.Element} this
20412      */
20413     setBottom : function(bottom){
20414         this.setStyle(BOTTOM, this.addUnits(bottom));
20415         return this;
20416     },
20417
20418     /**
20419      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
20420      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
20421      * @param {Number[]} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
20422      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20423      * @return {Ext.Element} this
20424      */
20425     setXY: function(pos, animate) {
20426         var me = this;
20427         if (!animate || !me.anim) {
20428             ELEMENT.setXY(me.dom, pos);
20429         }
20430         else {
20431             if (!Ext.isObject(animate)) {
20432                 animate = {};
20433             }
20434             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
20435         }
20436         return me;
20437     },
20438
20439     /**
20440      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
20441      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
20442      * @param {Number} x X value for new position (coordinates are page-based)
20443      * @param {Number} y Y value for new position (coordinates are page-based)
20444      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20445      * @return {Ext.Element} this
20446      */
20447     setLocation : function(x, y, animate){
20448         return this.setXY([x, y], animate);
20449     },
20450
20451     /**
20452      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
20453      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
20454      * @param {Number} x X value for new position (coordinates are page-based)
20455      * @param {Number} y Y value for new position (coordinates are page-based)
20456      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
20457      * @return {Ext.Element} this
20458      */
20459     moveTo : function(x, y, animate){
20460         return this.setXY([x, y], animate);
20461     },
20462
20463     /**
20464      * Gets the left X coordinate
20465      * @param {Boolean} local True to get the local css position instead of page coordinate
20466      * @return {Number}
20467      */
20468     getLeft : function(local){
20469         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
20470     },
20471
20472     /**
20473      * Gets the right X coordinate of the element (element X position + element width)
20474      * @param {Boolean} local True to get the local css position instead of page coordinate
20475      * @return {Number}
20476      */
20477     getRight : function(local){
20478         var me = this;
20479         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
20480     },
20481
20482     /**
20483      * Gets the top Y coordinate
20484      * @param {Boolean} local True to get the local css position instead of page coordinate
20485      * @return {Number}
20486      */
20487     getTop : function(local) {
20488         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
20489     },
20490
20491     /**
20492      * Gets the bottom Y coordinate of the element (element Y position + element height)
20493      * @param {Boolean} local True to get the local css position instead of page coordinate
20494      * @return {Number}
20495      */
20496     getBottom : function(local){
20497         var me = this;
20498         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
20499     },
20500
20501     /**
20502     * Initializes positioning on this element. If a desired position is not passed, it will make the
20503     * the element positioned relative IF it is not already positioned.
20504     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
20505     * @param {Number} zIndex (optional) The zIndex to apply
20506     * @param {Number} x (optional) Set the page X position
20507     * @param {Number} y (optional) Set the page Y position
20508     */
20509     position : function(pos, zIndex, x, y) {
20510         var me = this;
20511
20512         if (!pos && me.isStyle(POSITION, STATIC)){
20513             me.setStyle(POSITION, RELATIVE);
20514         } else if(pos) {
20515             me.setStyle(POSITION, pos);
20516         }
20517         if (zIndex){
20518             me.setStyle(ZINDEX, zIndex);
20519         }
20520         if (x || y) {
20521             me.setXY([x || false, y || false]);
20522         }
20523     },
20524
20525     /**
20526     * Clear positioning back to the default when the document was loaded
20527     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
20528     * @return {Ext.Element} this
20529      */
20530     clearPositioning : function(value){
20531         value = value || '';
20532         this.setStyle({
20533             left : value,
20534             right : value,
20535             top : value,
20536             bottom : value,
20537             "z-index" : "",
20538             position : STATIC
20539         });
20540         return this;
20541     },
20542
20543     /**
20544     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
20545     * snapshot before performing an update and then restoring the element.
20546     * @return {Object}
20547     */
20548     getPositioning : function(){
20549         var l = this.getStyle(LEFT);
20550         var t = this.getStyle(TOP);
20551         return {
20552             "position" : this.getStyle(POSITION),
20553             "left" : l,
20554             "right" : l ? "" : this.getStyle(RIGHT),
20555             "top" : t,
20556             "bottom" : t ? "" : this.getStyle(BOTTOM),
20557             "z-index" : this.getStyle(ZINDEX)
20558         };
20559     },
20560
20561     /**
20562     * Set positioning with an object returned by getPositioning().
20563     * @param {Object} posCfg
20564     * @return {Ext.Element} this
20565      */
20566     setPositioning : function(pc){
20567         var me = this,
20568             style = me.dom.style;
20569
20570         me.setStyle(pc);
20571
20572         if(pc.right == AUTO){
20573             style.right = "";
20574         }
20575         if(pc.bottom == AUTO){
20576             style.bottom = "";
20577         }
20578
20579         return me;
20580     },
20581
20582     /**
20583      * Translates the passed page coordinates into left/top css values for this element
20584      * @param {Number/Number[]} x The page x or an array containing [x, y]
20585      * @param {Number} y (optional) The page y, required if x is not an array
20586      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
20587      */
20588     translatePoints: function(x, y) {
20589         if (Ext.isArray(x)) {
20590              y = x[1];
20591              x = x[0];
20592         }
20593         var me = this,
20594             relative = me.isStyle(POSITION, RELATIVE),
20595             o = me.getXY(),
20596             left = parseInt(me.getStyle(LEFT), 10),
20597             top = parseInt(me.getStyle(TOP), 10);
20598
20599         if (!Ext.isNumber(left)) {
20600             left = relative ? 0 : me.dom.offsetLeft;
20601         }
20602         if (!Ext.isNumber(top)) {
20603             top = relative ? 0 : me.dom.offsetTop;
20604         }
20605         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
20606         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
20607         return {
20608             left: left,
20609             top: top
20610         };
20611     },
20612
20613     /**
20614      * 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.
20615      * @param {Object} box The box to fill {x, y, width, height}
20616      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
20617      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20618      * @return {Ext.Element} this
20619      */
20620     setBox: function(box, adjust, animate) {
20621         var me = this,
20622             w = box.width,
20623             h = box.height;
20624         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
20625             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
20626             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
20627         }
20628         me.setBounds(box.x, box.y, w, h, animate);
20629         return me;
20630     },
20631
20632     /**
20633      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20634      * set another Element's size/location to match this element.
20635      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
20636      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
20637      * @return {Object} box An object in the format<pre><code>
20638 {
20639     x: &lt;Element's X position>,
20640     y: &lt;Element's Y position>,
20641     width: &lt;Element's width>,
20642     height: &lt;Element's height>,
20643     bottom: &lt;Element's lower bound>,
20644     right: &lt;Element's rightmost bound>
20645 }
20646 </code></pre>
20647      * The returned object may also be addressed as an Array where index 0 contains the X position
20648      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20649      */
20650     getBox: function(contentBox, local) {
20651         var me = this,
20652             xy,
20653             left,
20654             top,
20655             getBorderWidth = me.getBorderWidth,
20656             getPadding = me.getPadding,
20657             l, r, t, b, w, h, bx;
20658         if (!local) {
20659             xy = me.getXY();
20660         } else {
20661             left = parseInt(me.getStyle("left"), 10) || 0;
20662             top = parseInt(me.getStyle("top"), 10) || 0;
20663             xy = [left, top];
20664         }
20665         w = me.getWidth();
20666         h = me.getHeight();
20667         if (!contentBox) {
20668             bx = {
20669                 x: xy[0],
20670                 y: xy[1],
20671                 0: xy[0],
20672                 1: xy[1],
20673                 width: w,
20674                 height: h
20675             };
20676         } else {
20677             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
20678             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
20679             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
20680             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
20681             bx = {
20682                 x: xy[0] + l,
20683                 y: xy[1] + t,
20684                 0: xy[0] + l,
20685                 1: xy[1] + t,
20686                 width: w - (l + r),
20687                 height: h - (t + b)
20688             };
20689         }
20690         bx.right = bx.x + bx.width;
20691         bx.bottom = bx.y + bx.height;
20692         return bx;
20693     },
20694
20695     /**
20696      * Move this element relative to its current position.
20697      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
20698      * @param {Number} distance How far to move the element in pixels
20699      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20700      */
20701     move: function(direction, distance, animate) {
20702         var me = this,
20703             xy = me.getXY(),
20704             x = xy[0],
20705             y = xy[1],
20706             left = [x - distance, y],
20707             right = [x + distance, y],
20708             top = [x, y - distance],
20709             bottom = [x, y + distance],
20710             hash = {
20711                 l: left,
20712                 left: left,
20713                 r: right,
20714                 right: right,
20715                 t: top,
20716                 top: top,
20717                 up: top,
20718                 b: bottom,
20719                 bottom: bottom,
20720                 down: bottom
20721             };
20722
20723         direction = direction.toLowerCase();
20724         me.moveTo(hash[direction][0], hash[direction][1], animate);
20725     },
20726
20727     /**
20728      * Quick set left and top adding default units
20729      * @param {String} left The left CSS property value
20730      * @param {String} top The top CSS property value
20731      * @return {Ext.Element} this
20732      */
20733     setLeftTop: function(left, top) {
20734         var me = this,
20735             style = me.dom.style;
20736         style.left = me.addUnits(left);
20737         style.top = me.addUnits(top);
20738         return me;
20739     },
20740
20741     /**
20742      * Returns the region of this element.
20743      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
20744      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20745      */
20746     getRegion: function() {
20747         return this.getPageBox(true);
20748     },
20749
20750     /**
20751      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
20752      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20753      */
20754     getViewRegion: function() {
20755         var me = this,
20756             isBody = me.dom === document.body,
20757             scroll, pos, top, left, width, height;
20758
20759         // For the body we want to do some special logic
20760         if (isBody) {
20761             scroll = me.getScroll();
20762             left = scroll.left;
20763             top = scroll.top;
20764             width = Ext.Element.getViewportWidth();
20765             height = Ext.Element.getViewportHeight();
20766         }
20767         else {
20768             pos = me.getXY();
20769             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
20770             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
20771             width = me.getWidth(true);
20772             height = me.getHeight(true);
20773         }
20774
20775         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
20776     },
20777
20778     /**
20779      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20780      * set another Element's size/location to match this element.
20781      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
20782      * @return {Object} box An object in the format<pre><code>
20783 {
20784     x: &lt;Element's X position>,
20785     y: &lt;Element's Y position>,
20786     width: &lt;Element's width>,
20787     height: &lt;Element's height>,
20788     bottom: &lt;Element's lower bound>,
20789     right: &lt;Element's rightmost bound>
20790 }
20791 </code></pre>
20792      * The returned object may also be addressed as an Array where index 0 contains the X position
20793      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20794      */
20795     getPageBox : function(getRegion) {
20796         var me = this,
20797             el = me.dom,
20798             isDoc = el === document.body,
20799             w = isDoc ? Ext.Element.getViewWidth()  : el.offsetWidth,
20800             h = isDoc ? Ext.Element.getViewHeight() : el.offsetHeight,
20801             xy = me.getXY(),
20802             t = xy[1],
20803             r = xy[0] + w,
20804             b = xy[1] + h,
20805             l = xy[0];
20806
20807         if (getRegion) {
20808             return Ext.create('Ext.util.Region', t, r, b, l);
20809         }
20810         else {
20811             return {
20812                 left: l,
20813                 top: t,
20814                 width: w,
20815                 height: h,
20816                 right: r,
20817                 bottom: b
20818             };
20819         }
20820     },
20821
20822     /**
20823      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
20824      * @param {Number} x X value for new position (coordinates are page-based)
20825      * @param {Number} y Y value for new position (coordinates are page-based)
20826      * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
20827      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20828      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
20829      * </ul></div>
20830      * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
20831      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20832      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
20833      * </ul></div>
20834      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20835      * @return {Ext.Element} this
20836      */
20837     setBounds: function(x, y, width, height, animate) {
20838         var me = this;
20839         if (!animate || !me.anim) {
20840             me.setSize(width, height);
20841             me.setLocation(x, y);
20842         } else {
20843             if (!Ext.isObject(animate)) {
20844                 animate = {};
20845             }
20846             me.animate(Ext.applyIf({
20847                 to: {
20848                     x: x,
20849                     y: y,
20850                     width: me.adjustWidth(width),
20851                     height: me.adjustHeight(height)
20852                 }
20853             }, animate));
20854         }
20855         return me;
20856     },
20857
20858     /**
20859      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
20860      * @param {Ext.util.Region} region The region to fill
20861      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20862      * @return {Ext.Element} this
20863      */
20864     setRegion: function(region, animate) {
20865         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
20866     }
20867 });
20868 })();
20869
20870 /**
20871  * @class Ext.Element
20872  */
20873 Ext.override(Ext.Element, {
20874     /**
20875      * Returns true if this element is scrollable.
20876      * @return {Boolean}
20877      */
20878     isScrollable : function(){
20879         var dom = this.dom;
20880         return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
20881     },
20882
20883     /**
20884      * Returns the current scroll position of the element.
20885      * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
20886      */
20887     getScroll : function() {
20888         var d = this.dom, 
20889             doc = document,
20890             body = doc.body,
20891             docElement = doc.documentElement,
20892             l,
20893             t,
20894             ret;
20895
20896         if (d == doc || d == body) {
20897             if (Ext.isIE && Ext.isStrict) {
20898                 l = docElement.scrollLeft; 
20899                 t = docElement.scrollTop;
20900             } else {
20901                 l = window.pageXOffset;
20902                 t = window.pageYOffset;
20903             }
20904             ret = {
20905                 left: l || (body ? body.scrollLeft : 0), 
20906                 top : t || (body ? body.scrollTop : 0)
20907             };
20908         } else {
20909             ret = {
20910                 left: d.scrollLeft, 
20911                 top : d.scrollTop
20912             };
20913         }
20914         
20915         return ret;
20916     },
20917     
20918     /**
20919      * 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().
20920      * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
20921      * @param {Number} value The new scroll value
20922      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20923      * @return {Ext.Element} this
20924      */
20925     scrollTo : function(side, value, animate) {
20926         //check if we're scrolling top or left
20927         var top = /top/i.test(side),
20928             me = this,
20929             dom = me.dom,
20930             obj = {},
20931             prop;
20932         if (!animate || !me.anim) {
20933             // just setting the value, so grab the direction
20934             prop = 'scroll' + (top ? 'Top' : 'Left');
20935             dom[prop] = value;
20936         }
20937         else {
20938             if (!Ext.isObject(animate)) {
20939                 animate = {};
20940             }
20941             obj['scroll' + (top ? 'Top' : 'Left')] = value;
20942             me.animate(Ext.applyIf({
20943                 to: obj
20944             }, animate));
20945         }
20946         return me;
20947     },
20948
20949     /**
20950      * Scrolls this element into view within the passed container.
20951      * @param {String/HTMLElement/Ext.Element} container (optional) The container element to scroll (defaults to document.body).  Should be a
20952      * string (id), dom node, or Ext.Element.
20953      * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
20954      * @return {Ext.Element} this
20955      */
20956     scrollIntoView : function(container, hscroll) {
20957         container = Ext.getDom(container) || Ext.getBody().dom;
20958         var el = this.dom,
20959             offsets = this.getOffsetsTo(container),
20960             // el's box
20961             left = offsets[0] + container.scrollLeft,
20962             top = offsets[1] + container.scrollTop,
20963             bottom = top + el.offsetHeight,
20964             right = left + el.offsetWidth,
20965             // ct's box
20966             ctClientHeight = container.clientHeight,
20967             ctScrollTop = parseInt(container.scrollTop, 10),
20968             ctScrollLeft = parseInt(container.scrollLeft, 10),
20969             ctBottom = ctScrollTop + ctClientHeight,
20970             ctRight = ctScrollLeft + container.clientWidth;
20971
20972         if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
20973             container.scrollTop = top;
20974         } else if (bottom > ctBottom) {
20975             container.scrollTop = bottom - ctClientHeight;
20976         }
20977         // corrects IE, other browsers will ignore
20978         container.scrollTop = container.scrollTop;
20979
20980         if (hscroll !== false) {
20981             if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
20982                 container.scrollLeft = left;
20983             }
20984             else if (right > ctRight) {
20985                 container.scrollLeft = right - container.clientWidth;
20986             }
20987             container.scrollLeft = container.scrollLeft;
20988         }
20989         return this;
20990     },
20991
20992     // private
20993     scrollChildIntoView : function(child, hscroll) {
20994         Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
20995     },
20996
20997     /**
20998      * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
20999      * within this element's scrollable range.
21000      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
21001      * @param {Number} distance How far to scroll the element in pixels
21002      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
21003      * @return {Boolean} Returns true if a scroll was triggered or false if the element
21004      * was scrolled as far as it could go.
21005      */
21006      scroll : function(direction, distance, animate) {
21007         if (!this.isScrollable()) {
21008             return false;
21009         }
21010         var el = this.dom,
21011             l = el.scrollLeft, t = el.scrollTop,
21012             w = el.scrollWidth, h = el.scrollHeight,
21013             cw = el.clientWidth, ch = el.clientHeight,
21014             scrolled = false, v,
21015             hash = {
21016                 l: Math.min(l + distance, w-cw),
21017                 r: v = Math.max(l - distance, 0),
21018                 t: Math.max(t - distance, 0),
21019                 b: Math.min(t + distance, h-ch)
21020             };
21021             hash.d = hash.b;
21022             hash.u = hash.t;
21023
21024         direction = direction.substr(0, 1);
21025         if ((v = hash[direction]) > -1) {
21026             scrolled = true;
21027             this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
21028         }
21029         return scrolled;
21030     }
21031 });
21032 /**
21033  * @class Ext.Element
21034  */
21035 Ext.Element.addMethods(
21036     function() {
21037         var VISIBILITY      = "visibility",
21038             DISPLAY         = "display",
21039             HIDDEN          = "hidden",
21040             NONE            = "none",
21041             XMASKED         = Ext.baseCSSPrefix + "masked",
21042             XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
21043             data            = Ext.Element.data;
21044
21045         return {
21046             /**
21047              * Checks whether the element is currently visible using both visibility and display properties.
21048              * @param {Boolean} [deep=false] True to walk the dom and see if parent elements are hidden
21049              * @return {Boolean} True if the element is currently visible, else false
21050              */
21051             isVisible : function(deep) {
21052                 var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
21053                     p   = this.dom.parentNode;
21054
21055                 if (deep !== true || !vis) {
21056                     return vis;
21057                 }
21058
21059                 while (p && !(/^body/i.test(p.tagName))) {
21060                     if (!Ext.fly(p, '_isVisible').isVisible()) {
21061                         return false;
21062                     }
21063                     p = p.parentNode;
21064                 }
21065                 return true;
21066             },
21067
21068             /**
21069              * Returns true if display is not "none"
21070              * @return {Boolean}
21071              */
21072             isDisplayed : function() {
21073                 return !this.isStyle(DISPLAY, NONE);
21074             },
21075
21076             /**
21077              * Convenience method for setVisibilityMode(Element.DISPLAY)
21078              * @param {String} display (optional) What to set display to when visible
21079              * @return {Ext.Element} this
21080              */
21081             enableDisplayMode : function(display) {
21082                 this.setVisibilityMode(Ext.Element.DISPLAY);
21083
21084                 if (!Ext.isEmpty(display)) {
21085                     data(this.dom, 'originalDisplay', display);
21086                 }
21087
21088                 return this;
21089             },
21090
21091             /**
21092              * Puts a mask over this element to disable user interaction. Requires core.css.
21093              * This method can only be applied to elements which accept child nodes.
21094              * @param {String} msg (optional) A message to display in the mask
21095              * @param {String} msgCls (optional) A css class to apply to the msg element
21096              * @return {Ext.Element} The mask element
21097              */
21098             mask : function(msg, msgCls) {
21099                 var me  = this,
21100                     dom = me.dom,
21101                     setExpression = dom.style.setExpression,
21102                     dh  = Ext.DomHelper,
21103                     EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
21104                     el,
21105                     mask;
21106
21107                 if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
21108                     me.addCls(XMASKEDRELATIVE);
21109                 }
21110                 el = data(dom, 'maskMsg');
21111                 if (el) {
21112                     el.remove();
21113                 }
21114                 el = data(dom, 'mask');
21115                 if (el) {
21116                     el.remove();
21117                 }
21118
21119                 mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
21120                 data(dom, 'mask', mask);
21121
21122                 me.addCls(XMASKED);
21123                 mask.setDisplayed(true);
21124
21125                 if (typeof msg == 'string') {
21126                     var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
21127                     data(dom, 'maskMsg', mm);
21128                     mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
21129                     mm.dom.firstChild.innerHTML = msg;
21130                     mm.setDisplayed(true);
21131                     mm.center(me);
21132                 }
21133                 // NOTE: CSS expressions are resource intensive and to be used only as a last resort
21134                 // These expressions are removed as soon as they are no longer necessary - in the unmask method.
21135                 // In normal use cases an element will be masked for a limited period of time.
21136                 // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
21137                 // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
21138                 if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
21139                     mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
21140                 }
21141
21142                 // Some versions and modes of IE subtract top+bottom padding when calculating height.
21143                 // Different versions from those which make the same error for width!
21144                 if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
21145                     mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
21146                 }
21147                 // ie will not expand full height automatically
21148                 else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
21149                     mask.setSize(undefined, me.getHeight());
21150                 }
21151                 return mask;
21152             },
21153
21154             /**
21155              * Removes a previously applied mask.
21156              */
21157             unmask : function() {
21158                 var me      = this,
21159                     dom     = me.dom,
21160                     mask    = data(dom, 'mask'),
21161                     maskMsg = data(dom, 'maskMsg');
21162
21163                 if (mask) {
21164                     // Remove resource-intensive CSS expressions as soon as they are not required.
21165                     if (mask.dom.style.clearExpression) {
21166                         mask.dom.style.clearExpression('width');
21167                         mask.dom.style.clearExpression('height');
21168                     }
21169                     if (maskMsg) {
21170                         maskMsg.remove();
21171                         data(dom, 'maskMsg', undefined);
21172                     }
21173
21174                     mask.remove();
21175                     data(dom, 'mask', undefined);
21176                     me.removeCls([XMASKED, XMASKEDRELATIVE]);
21177                 }
21178             },
21179             /**
21180              * Returns true if this element is masked. Also re-centers any displayed message within the mask.
21181              * @return {Boolean}
21182              */
21183             isMasked : function() {
21184                 var me = this,
21185                     mask = data(me.dom, 'mask'),
21186                     maskMsg = data(me.dom, 'maskMsg');
21187
21188                 if (mask && mask.isVisible()) {
21189                     if (maskMsg) {
21190                         maskMsg.center(me);
21191                     }
21192                     return true;
21193                 }
21194                 return false;
21195             },
21196
21197             /**
21198              * Creates an iframe shim for this element to keep selects and other windowed objects from
21199              * showing through.
21200              * @return {Ext.Element} The new shim element
21201              */
21202             createShim : function() {
21203                 var el = document.createElement('iframe'),
21204                     shim;
21205
21206                 el.frameBorder = '0';
21207                 el.className = Ext.baseCSSPrefix + 'shim';
21208                 el.src = Ext.SSL_SECURE_URL;
21209                 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
21210                 shim.autoBoxAdjust = false;
21211                 return shim;
21212             }
21213         };
21214     }()
21215 );
21216 /**
21217  * @class Ext.Element
21218  */
21219 Ext.Element.addMethods({
21220     /**
21221      * Convenience method for constructing a KeyMap
21222      * @param {String/Number/Number[]/Object} key Either a string with the keys to listen for, the numeric key code, array of key codes or an object with the following options:
21223      * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
21224      * @param {Function} fn The function to call
21225      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
21226      * @return {Ext.util.KeyMap} The KeyMap created
21227      */
21228     addKeyListener : function(key, fn, scope){
21229         var config;
21230         if(typeof key != 'object' || Ext.isArray(key)){
21231             config = {
21232                 key: key,
21233                 fn: fn,
21234                 scope: scope
21235             };
21236         }else{
21237             config = {
21238                 key : key.key,
21239                 shift : key.shift,
21240                 ctrl : key.ctrl,
21241                 alt : key.alt,
21242                 fn: fn,
21243                 scope: scope
21244             };
21245         }
21246         return Ext.create('Ext.util.KeyMap', this, config);
21247     },
21248
21249     /**
21250      * Creates a KeyMap for this element
21251      * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
21252      * @return {Ext.util.KeyMap} The KeyMap created
21253      */
21254     addKeyMap : function(config){
21255         return Ext.create('Ext.util.KeyMap', this, config);
21256     }
21257 });
21258
21259 //Import the newly-added Ext.Element functions into CompositeElementLite. We call this here because
21260 //Element.keys.js is the last extra Ext.Element include in the ext-all.js build
21261 Ext.CompositeElementLite.importElementMethods();
21262
21263 /**
21264  * @class Ext.CompositeElementLite
21265  */
21266 Ext.apply(Ext.CompositeElementLite.prototype, {
21267     addElements : function(els, root){
21268         if(!els){
21269             return this;
21270         }
21271         if(typeof els == "string"){
21272             els = Ext.Element.selectorFunction(els, root);
21273         }
21274         var yels = this.elements;
21275         Ext.each(els, function(e) {
21276             yels.push(Ext.get(e));
21277         });
21278         return this;
21279     },
21280
21281     /**
21282      * Returns the first Element
21283      * @return {Ext.Element}
21284      */
21285     first : function(){
21286         return this.item(0);
21287     },
21288
21289     /**
21290      * Returns the last Element
21291      * @return {Ext.Element}
21292      */
21293     last : function(){
21294         return this.item(this.getCount()-1);
21295     },
21296
21297     /**
21298      * Returns true if this composite contains the passed element
21299      * @param el {String/HTMLElement/Ext.Element/Number} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
21300      * @return Boolean
21301      */
21302     contains : function(el){
21303         return this.indexOf(el) != -1;
21304     },
21305
21306     /**
21307     * Removes the specified element(s).
21308     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
21309     * or an array of any of those.
21310     * @param {Boolean} removeDom (optional) True to also remove the element from the document
21311     * @return {Ext.CompositeElement} this
21312     */
21313     removeElement : function(keys, removeDom){
21314         var me = this,
21315             els = this.elements,
21316             el;
21317         Ext.each(keys, function(val){
21318             if ((el = (els[val] || els[val = me.indexOf(val)]))) {
21319                 if(removeDom){
21320                     if(el.dom){
21321                         el.remove();
21322                     }else{
21323                         Ext.removeNode(el);
21324                     }
21325                 }
21326                 Ext.Array.erase(els, val, 1);
21327             }
21328         });
21329         return this;
21330     }
21331 });
21332
21333 /**
21334  * @class Ext.CompositeElement
21335  * @extends Ext.CompositeElementLite
21336  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
21337  * members, or to perform collective actions upon the whole set.</p>
21338  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
21339  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
21340  * <p>All methods return <i>this</i> and can be chained.</p>
21341  * Usage:
21342 <pre><code>
21343 var els = Ext.select("#some-el div.some-class", true);
21344 // or select directly from an existing element
21345 var el = Ext.get('some-el');
21346 el.select('div.some-class', true);
21347
21348 els.setWidth(100); // all elements become 100 width
21349 els.hide(true); // all elements fade out and hide
21350 // or
21351 els.setWidth(100).hide(true);
21352 </code></pre>
21353  */
21354 Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
21355
21356     constructor : function(els, root){
21357         this.elements = [];
21358         this.add(els, root);
21359     },
21360
21361     // private
21362     getElement : function(el){
21363         // In this case just return it, since we already have a reference to it
21364         return el;
21365     },
21366
21367     // private
21368     transformElement : function(el){
21369         return Ext.get(el);
21370     }
21371 });
21372
21373 /**
21374  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
21375  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
21376  * {@link Ext.CompositeElementLite CompositeElementLite} object.
21377  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
21378  * @param {Boolean} [unique] true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
21379  * @param {HTMLElement/String} [root] The root element of the query or id of the root
21380  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
21381  * @member Ext.Element
21382  * @method select
21383  */
21384 Ext.Element.select = function(selector, unique, root){
21385     var els;
21386     if(typeof selector == "string"){
21387         els = Ext.Element.selectorFunction(selector, root);
21388     }else if(selector.length !== undefined){
21389         els = selector;
21390     }else{
21391         Ext.Error.raise({
21392             sourceClass: "Ext.Element",
21393             sourceMethod: "select",
21394             selector: selector,
21395             unique: unique,
21396             root: root,
21397             msg: "Invalid selector specified: " + selector
21398         });
21399     }
21400     return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
21401 };
21402
21403 /**
21404  * Shorthand of {@link Ext.Element#select}.
21405  * @member Ext
21406  * @method select
21407  * @alias Ext.Element#select
21408  */
21409 Ext.select = Ext.Element.select;
21410
21411
21412 /*
21413
21414 This file is part of Ext JS 4
21415
21416 Copyright (c) 2011 Sencha Inc
21417
21418 Contact:  http://www.sencha.com/contact
21419
21420 GNU General Public License Usage
21421 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
21422
21423 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
21424
21425 */
21426 /**
21427  * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
21428  * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
21429  *
21430  * For example:
21431  *
21432  *     Ext.define('Employee', {
21433  *         extend: 'Ext.util.Observable',
21434  *         constructor: function(config){
21435  *             this.name = config.name;
21436  *             this.addEvents({
21437  *                 "fired" : true,
21438  *                 "quit" : true
21439  *             });
21440  *
21441  *             // Copy configured listeners into *this* object so that the base class's
21442  *             // constructor will add them.
21443  *             this.listeners = config.listeners;
21444  *
21445  *             // Call our superclass constructor to complete construction process.
21446  *             this.callParent(arguments)
21447  *         }
21448  *     });
21449  *
21450  * This could then be used like this:
21451  *
21452  *     var newEmployee = new Employee({
21453  *         name: employeeName,
21454  *         listeners: {
21455  *             quit: function() {
21456  *                 // By default, "this" will be the object that fired the event.
21457  *                 alert(this.name + " has quit!");
21458  *             }
21459  *         }
21460  *     });
21461  */
21462 Ext.define('Ext.util.Observable', {
21463
21464     /* Begin Definitions */
21465
21466     requires: ['Ext.util.Event'],
21467
21468     statics: {
21469         /**
21470          * Removes **all** added captures from the Observable.
21471          *
21472          * @param {Ext.util.Observable} o The Observable to release
21473          * @static
21474          */
21475         releaseCapture: function(o) {
21476             o.fireEvent = this.prototype.fireEvent;
21477         },
21478
21479         /**
21480          * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
21481          * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
21482          * the event will not fire.
21483          *
21484          * @param {Ext.util.Observable} o The Observable to capture events from.
21485          * @param {Function} fn The function to call when an event is fired.
21486          * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
21487          * the Observable firing the event.
21488          * @static
21489          */
21490         capture: function(o, fn, scope) {
21491             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
21492         },
21493
21494         /**
21495          * Sets observability on the passed class constructor.
21496          *
21497          * This makes any event fired on any instance of the passed class also fire a single event through
21498          * the **class** allowing for central handling of events on many instances at once.
21499          *
21500          * Usage:
21501          *
21502          *     Ext.util.Observable.observe(Ext.data.Connection);
21503          *     Ext.data.Connection.on('beforerequest', function(con, options) {
21504          *         console.log('Ajax request made to ' + options.url);
21505          *     });
21506          *
21507          * @param {Function} c The class constructor to make observable.
21508          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
21509          * @static
21510          */
21511         observe: function(cls, listeners) {
21512             if (cls) {
21513                 if (!cls.isObservable) {
21514                     Ext.applyIf(cls, new this());
21515                     this.capture(cls.prototype, cls.fireEvent, cls);
21516                 }
21517                 if (Ext.isObject(listeners)) {
21518                     cls.on(listeners);
21519                 }
21520                 return cls;
21521             }
21522         }
21523     },
21524
21525     /* End Definitions */
21526
21527     /**
21528      * @cfg {Object} listeners
21529      *
21530      * A config object containing one or more event handlers to be added to this object during initialization. This
21531      * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
21532      * handlers at once.
21533      *
21534      * **DOM events from Ext JS {@link Ext.Component Components}**
21535      *
21536      * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
21537      * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
21538      * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
21539      * child element of a Component, we need to specify the `element` option to identify the Component property to add a
21540      * DOM listener to:
21541      *
21542      *     new Ext.panel.Panel({
21543      *         width: 400,
21544      *         height: 200,
21545      *         dockedItems: [{
21546      *             xtype: 'toolbar'
21547      *         }],
21548      *         listeners: {
21549      *             click: {
21550      *                 element: 'el', //bind to the underlying el property on the panel
21551      *                 fn: function(){ console.log('click el'); }
21552      *             },
21553      *             dblclick: {
21554      *                 element: 'body', //bind to the underlying body property on the panel
21555      *                 fn: function(){ console.log('dblclick body'); }
21556      *             }
21557      *         }
21558      *     });
21559      */
21560     // @private
21561     isObservable: true,
21562
21563     constructor: function(config) {
21564         var me = this;
21565
21566         Ext.apply(me, config);
21567         if (me.listeners) {
21568             me.on(me.listeners);
21569             delete me.listeners;
21570         }
21571         me.events = me.events || {};
21572
21573         if (me.bubbleEvents) {
21574             me.enableBubble(me.bubbleEvents);
21575         }
21576     },
21577
21578     // @private
21579     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
21580
21581     /**
21582      * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
21583      * destroyed.
21584      *
21585      * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
21586      * @param {Object/String} ename The event name, or an object containing event name properties.
21587      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21588      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21589      * in which the handler function is executed.
21590      * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
21591      * {@link Ext.util.Observable#addListener addListener} options.
21592      */
21593     addManagedListener : function(item, ename, fn, scope, options) {
21594         var me = this,
21595             managedListeners = me.managedListeners = me.managedListeners || [],
21596             config;
21597
21598         if (typeof ename !== 'string') {
21599             options = ename;
21600             for (ename in options) {
21601                 if (options.hasOwnProperty(ename)) {
21602                     config = options[ename];
21603                     if (!me.eventOptionsRe.test(ename)) {
21604                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21605                     }
21606                 }
21607             }
21608         }
21609         else {
21610             managedListeners.push({
21611                 item: item,
21612                 ename: ename,
21613                 fn: fn,
21614                 scope: scope,
21615                 options: options
21616             });
21617
21618             item.on(ename, fn, scope, options);
21619         }
21620     },
21621
21622     /**
21623      * Removes listeners that were added by the {@link #mon} method.
21624      *
21625      * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
21626      * @param {Object/String} ename The event name, or an object containing event name properties.
21627      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21628      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21629      * in which the handler function is executed.
21630      */
21631     removeManagedListener : function(item, ename, fn, scope) {
21632         var me = this,
21633             options,
21634             config,
21635             managedListeners,
21636             length,
21637             i;
21638
21639         if (typeof ename !== 'string') {
21640             options = ename;
21641             for (ename in options) {
21642                 if (options.hasOwnProperty(ename)) {
21643                     config = options[ename];
21644                     if (!me.eventOptionsRe.test(ename)) {
21645                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
21646                     }
21647                 }
21648             }
21649         }
21650
21651         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
21652
21653         for (i = 0, length = managedListeners.length; i < length; i++) {
21654             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
21655         }
21656     },
21657
21658     /**
21659      * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
21660      * to {@link #addListener}).
21661      *
21662      * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
21663      * calling {@link #enableBubble}.
21664      *
21665      * @param {String} eventName The name of the event to fire.
21666      * @param {Object...} args Variable number of parameters are passed to handlers.
21667      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
21668      */
21669     fireEvent: function(eventName) {
21670         var name = eventName.toLowerCase(),
21671             events = this.events,
21672             event = events && events[name],
21673             bubbles = event && event.bubble;
21674
21675         return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
21676     },
21677
21678     /**
21679      * Continue to fire event.
21680      * @private
21681      *
21682      * @param {String} eventName
21683      * @param {Array} args
21684      * @param {Boolean} bubbles
21685      */
21686     continueFireEvent: function(eventName, args, bubbles) {
21687         var target = this,
21688             queue, event,
21689             ret = true;
21690
21691         do {
21692             if (target.eventsSuspended === true) {
21693                 if ((queue = target.eventQueue)) {
21694                     queue.push([eventName, args, bubbles]);
21695                 }
21696                 return ret;
21697             } else {
21698                 event = target.events[eventName];
21699                 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
21700                 // configure to bubble.
21701                 if (event && event != true) {
21702                     if ((ret = event.fire.apply(event, args)) === false) {
21703                         break;
21704                     }
21705                 }
21706             }
21707         } while (bubbles && (target = target.getBubbleParent()));
21708         return ret;
21709     },
21710
21711     /**
21712      * Gets the bubbling parent for an Observable
21713      * @private
21714      * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
21715      */
21716     getBubbleParent: function(){
21717         var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
21718         if (parent && parent.isObservable) {
21719             return parent;
21720         }
21721         return null;
21722     },
21723
21724     /**
21725      * Appends an event handler to this object.
21726      *
21727      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
21728      * event names.
21729      * @param {Function} fn The method the event invokes.  Will be called with arguments given to
21730      * {@link #fireEvent} plus the `options` parameter described below.
21731      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
21732      * omitted, defaults to the object which fired the event.**
21733      * @param {Object} [options] An object containing handler configuration.
21734      *
21735      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
21736      *
21737      * This object may contain any of the following properties:
21738      *
21739      * - **scope** : Object
21740      *
21741      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
21742      *   which fired the event.**
21743      *
21744      * - **delay** : Number
21745      *
21746      *   The number of milliseconds to delay the invocation of the handler after the event fires.
21747      *
21748      * - **single** : Boolean
21749      *
21750      *   True to add a handler to handle just the next firing of the event, and then remove itself.
21751      *
21752      * - **buffer** : Number
21753      *
21754      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
21755      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
21756      *   handler is scheduled in its place.
21757      *
21758      * - **target** : Observable
21759      *
21760      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
21761      *   child Observable.
21762      *
21763      * - **element** : String
21764      *
21765      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
21766      *   property which references an element to add a listener to.
21767      *
21768      *   This option is useful during Component construction to add DOM event listeners to elements of
21769      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
21770      *   For example, to add a click listener to a Panel's body:
21771      *
21772      *       new Ext.panel.Panel({
21773      *           title: 'The title',
21774      *           listeners: {
21775      *               click: this.handlePanelClick,
21776      *               element: 'body'
21777      *           }
21778      *       });
21779      *
21780      * **Combining Options**
21781      *
21782      * Using the options argument, it is possible to combine different types of listeners:
21783      *
21784      * A delayed, one-time listener.
21785      *
21786      *     myPanel.on('hide', this.handleClick, this, {
21787      *         single: true,
21788      *         delay: 100
21789      *     });
21790      *
21791      * **Attaching multiple handlers in 1 call**
21792      *
21793      * The method also allows for a single argument to be passed which is a config object containing properties which
21794      * specify multiple events. For example:
21795      *
21796      *     myGridPanel.on({
21797      *         cellClick: this.onCellClick,
21798      *         mouseover: this.onMouseOver,
21799      *         mouseout: this.onMouseOut,
21800      *         scope: this // Important. Ensure "this" is correct during handler execution
21801      *     });
21802      *
21803      * One can also specify options for each event handler separately:
21804      *
21805      *     myGridPanel.on({
21806      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
21807      *         mouseover: {fn: panel.onMouseOver, scope: panel}
21808      *     });
21809      *
21810      */
21811     addListener: function(ename, fn, scope, options) {
21812         var me = this,
21813             config,
21814             event;
21815
21816         if (typeof ename !== 'string') {
21817             options = ename;
21818             for (ename in options) {
21819                 if (options.hasOwnProperty(ename)) {
21820                     config = options[ename];
21821                     if (!me.eventOptionsRe.test(ename)) {
21822                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21823                     }
21824                 }
21825             }
21826         }
21827         else {
21828             ename = ename.toLowerCase();
21829             me.events[ename] = me.events[ename] || true;
21830             event = me.events[ename] || true;
21831             if (Ext.isBoolean(event)) {
21832                 me.events[ename] = event = new Ext.util.Event(me, ename);
21833             }
21834             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
21835         }
21836     },
21837
21838     /**
21839      * Removes an event handler.
21840      *
21841      * @param {String} eventName The type of event the handler was associated with.
21842      * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
21843      * {@link #addListener} call.**
21844      * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
21845      * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
21846      */
21847     removeListener: function(ename, fn, scope) {
21848         var me = this,
21849             config,
21850             event,
21851             options;
21852
21853         if (typeof ename !== 'string') {
21854             options = ename;
21855             for (ename in options) {
21856                 if (options.hasOwnProperty(ename)) {
21857                     config = options[ename];
21858                     if (!me.eventOptionsRe.test(ename)) {
21859                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
21860                     }
21861                 }
21862             }
21863         } else {
21864             ename = ename.toLowerCase();
21865             event = me.events[ename];
21866             if (event && event.isEvent) {
21867                 event.removeListener(fn, scope);
21868             }
21869         }
21870     },
21871
21872     /**
21873      * Removes all listeners for this object including the managed listeners
21874      */
21875     clearListeners: function() {
21876         var events = this.events,
21877             event,
21878             key;
21879
21880         for (key in events) {
21881             if (events.hasOwnProperty(key)) {
21882                 event = events[key];
21883                 if (event.isEvent) {
21884                     event.clearListeners();
21885                 }
21886             }
21887         }
21888
21889         this.clearManagedListeners();
21890     },
21891
21892     purgeListeners : function() {
21893         if (Ext.global.console) {
21894             Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
21895         }
21896         return this.clearListeners.apply(this, arguments);
21897     },
21898
21899     /**
21900      * Removes all managed listeners for this object.
21901      */
21902     clearManagedListeners : function() {
21903         var managedListeners = this.managedListeners || [],
21904             i = 0,
21905             len = managedListeners.length;
21906
21907         for (; i < len; i++) {
21908             this.removeManagedListenerItem(true, managedListeners[i]);
21909         }
21910
21911         this.managedListeners = [];
21912     },
21913
21914     /**
21915      * Remove a single managed listener item
21916      * @private
21917      * @param {Boolean} isClear True if this is being called during a clear
21918      * @param {Object} managedListener The managed listener item
21919      * See removeManagedListener for other args
21920      */
21921     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
21922         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
21923             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
21924             if (!isClear) {
21925                 Ext.Array.remove(this.managedListeners, managedListener);
21926             }
21927         }
21928     },
21929
21930     purgeManagedListeners : function() {
21931         if (Ext.global.console) {
21932             Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
21933         }
21934         return this.clearManagedListeners.apply(this, arguments);
21935     },
21936
21937     /**
21938      * Adds the specified events to the list of events which this Observable may fire.
21939      *
21940      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
21941      * event name string if multiple event names are being passed as separate parameters. Usage:
21942      *
21943      *     this.addEvents({
21944      *         storeloaded: true,
21945      *         storecleared: true
21946      *     });
21947      *
21948      * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
21949      * parameters. Usage:
21950      *
21951      *     this.addEvents('storeloaded', 'storecleared');
21952      *
21953      */
21954     addEvents: function(o) {
21955         var me = this,
21956             args,
21957             len,
21958             i;
21959
21960             me.events = me.events || {};
21961         if (Ext.isString(o)) {
21962             args = arguments;
21963             i = args.length;
21964
21965             while (i--) {
21966                 me.events[args[i]] = me.events[args[i]] || true;
21967             }
21968         } else {
21969             Ext.applyIf(me.events, o);
21970         }
21971     },
21972
21973     /**
21974      * Checks to see if this object has any listeners for a specified event
21975      *
21976      * @param {String} eventName The name of the event to check for
21977      * @return {Boolean} True if the event is being listened for, else false
21978      */
21979     hasListener: function(ename) {
21980         var event = this.events[ename.toLowerCase()];
21981         return event && event.isEvent === true && event.listeners.length > 0;
21982     },
21983
21984     /**
21985      * Suspends the firing of all events. (see {@link #resumeEvents})
21986      *
21987      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
21988      * after the {@link #resumeEvents} call instead of discarding all suspended events.
21989      */
21990     suspendEvents: function(queueSuspended) {
21991         this.eventsSuspended = true;
21992         if (queueSuspended && !this.eventQueue) {
21993             this.eventQueue = [];
21994         }
21995     },
21996
21997     /**
21998      * Resumes firing events (see {@link #suspendEvents}).
21999      *
22000      * If events were suspended using the `queueSuspended` parameter, then all events fired
22001      * during event suspension will be sent to any listeners now.
22002      */
22003     resumeEvents: function() {
22004         var me = this,
22005             queued = me.eventQueue;
22006
22007         me.eventsSuspended = false;
22008         delete me.eventQueue;
22009
22010         if (queued) {
22011             Ext.each(queued, function(e) {
22012                 me.continueFireEvent.apply(me, e);
22013             });
22014         }
22015     },
22016
22017     /**
22018      * Relays selected events from the specified Observable as if the events were fired by `this`.
22019      *
22020      * @param {Object} origin The Observable whose events this object is to relay.
22021      * @param {String[]} events Array of event names to relay.
22022      * @param {String} prefix
22023      */
22024     relayEvents : function(origin, events, prefix) {
22025         prefix = prefix || '';
22026         var me = this,
22027             len = events.length,
22028             i = 0,
22029             oldName,
22030             newName;
22031
22032         for (; i < len; i++) {
22033             oldName = events[i].substr(prefix.length);
22034             newName = prefix + oldName;
22035             me.events[newName] = me.events[newName] || true;
22036             origin.on(oldName, me.createRelayer(newName));
22037         }
22038     },
22039
22040     /**
22041      * @private
22042      * Creates an event handling function which refires the event from this object as the passed event name.
22043      * @param newName
22044      * @returns {Function}
22045      */
22046     createRelayer: function(newName){
22047         var me = this;
22048         return function(){
22049             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
22050         };
22051     },
22052
22053     /**
22054      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
22055      * present. There is no implementation in the Observable base class.
22056      *
22057      * This is commonly used by Ext.Components to bubble events to owner Containers.
22058      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
22059      * Component's immediate owner. But if a known target is required, this can be overridden to access the
22060      * required target more quickly.
22061      *
22062      * Example:
22063      *
22064      *     Ext.override(Ext.form.field.Base, {
22065      *         //  Add functionality to Field's initComponent to enable the change event to bubble
22066      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
22067      *             this.enableBubble('change');
22068      *         }),
22069      *
22070      *         //  We know that we want Field's events to bubble directly to the FormPanel.
22071      *         getBubbleTarget : function() {
22072      *             if (!this.formPanel) {
22073      *                 this.formPanel = this.findParentByType('form');
22074      *             }
22075      *             return this.formPanel;
22076      *         }
22077      *     });
22078      *
22079      *     var myForm = new Ext.formPanel({
22080      *         title: 'User Details',
22081      *         items: [{
22082      *             ...
22083      *         }],
22084      *         listeners: {
22085      *             change: function() {
22086      *                 // Title goes red if form has been modified.
22087      *                 myForm.header.setStyle('color', 'red');
22088      *             }
22089      *         }
22090      *     });
22091      *
22092      * @param {String/String[]} events The event name to bubble, or an Array of event names.
22093      */
22094     enableBubble: function(events) {
22095         var me = this;
22096         if (!Ext.isEmpty(events)) {
22097             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
22098             Ext.each(events,
22099             function(ename) {
22100                 ename = ename.toLowerCase();
22101                 var ce = me.events[ename] || true;
22102                 if (Ext.isBoolean(ce)) {
22103                     ce = new Ext.util.Event(me, ename);
22104                     me.events[ename] = ce;
22105                 }
22106                 ce.bubble = true;
22107             });
22108         }
22109     }
22110 }, function() {
22111
22112     this.createAlias({
22113         /**
22114          * @method
22115          * Shorthand for {@link #addListener}.
22116          * @alias Ext.util.Observable#addListener
22117          */
22118         on: 'addListener',
22119         /**
22120          * @method
22121          * Shorthand for {@link #removeListener}.
22122          * @alias Ext.util.Observable#removeListener
22123          */
22124         un: 'removeListener',
22125         /**
22126          * @method
22127          * Shorthand for {@link #addManagedListener}.
22128          * @alias Ext.util.Observable#addManagedListener
22129          */
22130         mon: 'addManagedListener',
22131         /**
22132          * @method
22133          * Shorthand for {@link #removeManagedListener}.
22134          * @alias Ext.util.Observable#removeManagedListener
22135          */
22136         mun: 'removeManagedListener'
22137     });
22138
22139     //deprecated, will be removed in 5.0
22140     this.observeClass = this.observe;
22141
22142     Ext.apply(Ext.util.Observable.prototype, function(){
22143         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
22144         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
22145         // private
22146         function getMethodEvent(method){
22147             var e = (this.methodEvents = this.methodEvents || {})[method],
22148                 returnValue,
22149                 v,
22150                 cancel,
22151                 obj = this;
22152
22153             if (!e) {
22154                 this.methodEvents[method] = e = {};
22155                 e.originalFn = this[method];
22156                 e.methodName = method;
22157                 e.before = [];
22158                 e.after = [];
22159
22160                 var makeCall = function(fn, scope, args){
22161                     if((v = fn.apply(scope || obj, args)) !== undefined){
22162                         if (typeof v == 'object') {
22163                             if(v.returnValue !== undefined){
22164                                 returnValue = v.returnValue;
22165                             }else{
22166                                 returnValue = v;
22167                             }
22168                             cancel = !!v.cancel;
22169                         }
22170                         else
22171                             if (v === false) {
22172                                 cancel = true;
22173                             }
22174                             else {
22175                                 returnValue = v;
22176                             }
22177                     }
22178                 };
22179
22180                 this[method] = function(){
22181                     var args = Array.prototype.slice.call(arguments, 0),
22182                         b, i, len;
22183                     returnValue = v = undefined;
22184                     cancel = false;
22185
22186                     for(i = 0, len = e.before.length; i < len; i++){
22187                         b = e.before[i];
22188                         makeCall(b.fn, b.scope, args);
22189                         if (cancel) {
22190                             return returnValue;
22191                         }
22192                     }
22193
22194                     if((v = e.originalFn.apply(obj, args)) !== undefined){
22195                         returnValue = v;
22196                     }
22197
22198                     for(i = 0, len = e.after.length; i < len; i++){
22199                         b = e.after[i];
22200                         makeCall(b.fn, b.scope, args);
22201                         if (cancel) {
22202                             return returnValue;
22203                         }
22204                     }
22205                     return returnValue;
22206                 };
22207             }
22208             return e;
22209         }
22210
22211         return {
22212             // these are considered experimental
22213             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
22214             // adds an 'interceptor' called before the original method
22215             beforeMethod : function(method, fn, scope){
22216                 getMethodEvent.call(this, method).before.push({
22217                     fn: fn,
22218                     scope: scope
22219                 });
22220             },
22221
22222             // adds a 'sequence' called after the original method
22223             afterMethod : function(method, fn, scope){
22224                 getMethodEvent.call(this, method).after.push({
22225                     fn: fn,
22226                     scope: scope
22227                 });
22228             },
22229
22230             removeMethodListener: function(method, fn, scope){
22231                 var e = this.getMethodEvent(method),
22232                     i, len;
22233                 for(i = 0, len = e.before.length; i < len; i++){
22234                     if(e.before[i].fn == fn && e.before[i].scope == scope){
22235                         Ext.Array.erase(e.before, i, 1);
22236                         return;
22237                     }
22238                 }
22239                 for(i = 0, len = e.after.length; i < len; i++){
22240                     if(e.after[i].fn == fn && e.after[i].scope == scope){
22241                         Ext.Array.erase(e.after, i, 1);
22242                         return;
22243                     }
22244                 }
22245             },
22246
22247             toggleEventLogging: function(toggle) {
22248                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
22249                     if (Ext.isDefined(Ext.global.console)) {
22250                         Ext.global.console.log(en, arguments);
22251                     }
22252                 });
22253             }
22254         };
22255     }());
22256 });
22257
22258 /**
22259  * @class Ext.util.Animate
22260  * This animation class is a mixin.
22261  * 
22262  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
22263  * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement}, 
22264  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
22265  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
22266  * opacity (color, paddings, and margins can not be animated).
22267  * 
22268  * ## Animation Basics
22269  * 
22270  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
22271  * you wish to animate. Easing and duration are defaulted values specified below.
22272  * Easing describes how the intermediate values used during a transition will be calculated. 
22273  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
22274  * You may use the defaults for easing and duration, but you must always set a 
22275  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
22276  * 
22277  * Popular element 'to' configurations are:
22278  * 
22279  *  - opacity
22280  *  - x
22281  *  - y
22282  *  - color
22283  *  - height
22284  *  - width 
22285  * 
22286  * Popular sprite 'to' configurations are:
22287  * 
22288  *  - translation
22289  *  - path
22290  *  - scale
22291  *  - stroke
22292  *  - rotation
22293  * 
22294  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
22295  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
22296  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
22297  * 
22298  * For example, a simple animation to fade out an element with a default easing and duration:
22299  * 
22300  *     var p1 = Ext.get('myElementId');
22301  * 
22302  *     p1.animate({
22303  *         to: {
22304  *             opacity: 0
22305  *         }
22306  *     });
22307  * 
22308  * To make this animation fade out in a tenth of a second:
22309  * 
22310  *     var p1 = Ext.get('myElementId');
22311  * 
22312  *     p1.animate({
22313  *        duration: 100,
22314  *         to: {
22315  *             opacity: 0
22316  *         }
22317  *     });
22318  * 
22319  * ## Animation Queues
22320  * 
22321  * By default all animations are added to a queue which allows for animation via a chain-style API.
22322  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
22323  * 
22324  *     p1.animate({
22325  *         to: {
22326  *             x: 500
22327  *         }
22328  *     }).animate({
22329  *         to: {
22330  *             y: 150
22331  *         }
22332  *     }).animate({
22333  *         to: {
22334  *             backgroundColor: '#f00'  //red
22335  *         }
22336  *     }).animate({
22337  *         to: {
22338  *             opacity: 0
22339  *         }
22340  *     });
22341  * 
22342  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
22343  * subsequent animations for the specified target will be run concurrently (at the same time).
22344  * 
22345  *     p1.syncFx();  //this will make all animations run at the same time
22346  * 
22347  *     p1.animate({
22348  *         to: {
22349  *             x: 500
22350  *         }
22351  *     }).animate({
22352  *         to: {
22353  *             y: 150
22354  *         }
22355  *     }).animate({
22356  *         to: {
22357  *             backgroundColor: '#f00'  //red
22358  *         }
22359  *     }).animate({
22360  *         to: {
22361  *             opacity: 0
22362  *         }
22363  *     });
22364  * 
22365  * This works the same as:
22366  * 
22367  *     p1.animate({
22368  *         to: {
22369  *             x: 500,
22370  *             y: 150,
22371  *             backgroundColor: '#f00'  //red
22372  *             opacity: 0
22373  *         }
22374  *     });
22375  * 
22376  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
22377  * currently running animations and clear any queued animations. 
22378  * 
22379  * ## Animation Keyframes
22380  *
22381  * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the 
22382  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
22383  * The previous example can be written with the following syntax:
22384  * 
22385  *     p1.animate({
22386  *         duration: 1000,  //one second total
22387  *         keyframes: {
22388  *             25: {     //from 0 to 250ms (25%)
22389  *                 x: 0
22390  *             },
22391  *             50: {   //from 250ms to 500ms (50%)
22392  *                 y: 0
22393  *             },
22394  *             75: {  //from 500ms to 750ms (75%)
22395  *                 backgroundColor: '#f00'  //red
22396  *             },
22397  *             100: {  //from 750ms to 1sec
22398  *                 opacity: 0
22399  *             }
22400  *         }
22401  *     });
22402  * 
22403  * ## Animation Events
22404  * 
22405  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate}, 
22406  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
22407  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
22408  * fires for each keyframe in your animation.
22409  * 
22410  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
22411  *    
22412  *     startAnimate: function() {
22413  *         var p1 = Ext.get('myElementId');
22414  *         p1.animate({
22415  *            duration: 100,
22416  *             to: {
22417  *                 opacity: 0
22418  *             },
22419  *             listeners: {
22420  *                 beforeanimate:  function() {
22421  *                     // Execute my custom method before the animation
22422  *                     this.myBeforeAnimateFn();
22423  *                 },
22424  *                 afteranimate: function() {
22425  *                     // Execute my custom method after the animation
22426  *                     this.myAfterAnimateFn();
22427  *                 },
22428  *                 scope: this
22429  *         });
22430  *     },
22431  *     myBeforeAnimateFn: function() {
22432  *       // My custom logic
22433  *     },
22434  *     myAfterAnimateFn: function() {
22435  *       // My custom logic
22436  *     }
22437  * 
22438  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
22439  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
22440  * method.  This method will return false if there are no active animations or return the currently 
22441  * running {@link Ext.fx.Anim} instance.
22442  * 
22443  * In this example, we're going to wait for the current animation to finish, then stop any other 
22444  * queued animations before we fade our element's opacity to 0:
22445  * 
22446  *     var curAnim = p1.getActiveAnimation();
22447  *     if (curAnim) {
22448  *         curAnim.on('afteranimate', function() {
22449  *             p1.stopAnimation();
22450  *             p1.animate({
22451  *                 to: {
22452  *                     opacity: 0
22453  *                 }
22454  *             });
22455  *         });
22456  *     }
22457  * 
22458  * @docauthor Jamie Avins <jamie@sencha.com>
22459  */
22460 Ext.define('Ext.util.Animate', {
22461
22462     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
22463
22464     /**
22465      * <p>Perform custom animation on this object.<p>
22466      * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
22467      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
22468      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
22469      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
22470      * <p>Properties include<ul>
22471      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
22472      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
22473      * ths object being animated. See the sections below on Element and Component animation.<div></li>
22474      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
22475      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
22476      * <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>
22477      * <li>ease</li>
22478      * <li>easeIn</li>
22479      * <li>easeOut</li>
22480      * <li>easeInOut</li>
22481      * <li>backIn</li>
22482      * <li>backOut</li>
22483      * <li>elasticIn</li>
22484      * <li>elasticOut</li>
22485      * <li>bounceIn</li>
22486      * <li>bounceOut</li>
22487      * </ul></code></div></li>
22488      * <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.
22489      * 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>
22490      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
22491      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
22492      * </ul></p>
22493      * <h3>Animating an {@link Ext.Element Element}</h3>
22494      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
22495      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
22496      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
22497      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
22498      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
22499      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
22500      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
22501      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
22502      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
22503      * <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>
22504      * </ul>
22505      * <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
22506      * 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
22507      * directly animate certain properties of Components.</b></p>
22508      * <h3>Animating a {@link Ext.Component Component}</h3>
22509      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
22510      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
22511      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
22512      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
22513      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
22514      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
22515      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
22516      * <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
22517      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
22518      * </ul>
22519      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
22520      * <pre><code>
22521 myWindow = Ext.create('Ext.window.Window', {
22522     title: 'Test Component animation',
22523     width: 500,
22524     height: 300,
22525     layout: {
22526         type: 'hbox',
22527         align: 'stretch'
22528     },
22529     items: [{
22530         title: 'Left: 33%',
22531         margins: '5 0 5 5',
22532         flex: 1
22533     }, {
22534         title: 'Left: 66%',
22535         margins: '5 5 5 5',
22536         flex: 2
22537     }]
22538 });
22539 myWindow.show();
22540 myWindow.header.el.on('click', function() {
22541     myWindow.animate({
22542         to: {
22543             width: (myWindow.getWidth() == 500) ? 700 : 500,
22544             height: (myWindow.getHeight() == 300) ? 400 : 300,
22545         }
22546     });
22547 });
22548 </code></pre>
22549      * <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
22550      * 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>
22551      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
22552      * @return {Object} this
22553      */
22554     animate: function(animObj) {
22555         var me = this;
22556         if (Ext.fx.Manager.hasFxBlock(me.id)) {
22557             return me;
22558         }
22559         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
22560         return this;
22561     },
22562
22563     // @private - process the passed fx configuration.
22564     anim: function(config) {
22565         if (!Ext.isObject(config)) {
22566             return (config) ? {} : false;
22567         }
22568
22569         var me = this;
22570
22571         if (config.stopAnimation) {
22572             me.stopAnimation();
22573         }
22574
22575         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
22576
22577         return Ext.apply({
22578             target: me,
22579             paused: true
22580         }, config);
22581     },
22582
22583     /**
22584      * @deprecated 4.0 Replaced by {@link #stopAnimation}
22585      * Stops any running effects and clears this object's internal effects queue if it contains
22586      * any additional effects that haven't started yet.
22587      * @return {Ext.Element} The Element
22588      * @method
22589      */
22590     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
22591
22592     /**
22593      * Stops any running effects and clears this object's internal effects queue if it contains
22594      * any additional effects that haven't started yet.
22595      * @return {Ext.Element} The Element
22596      */
22597     stopAnimation: function() {
22598         Ext.fx.Manager.stopAnimation(this.id);
22599         return this;
22600     },
22601
22602     /**
22603      * Ensures that all effects queued after syncFx is called on this object are
22604      * run concurrently.  This is the opposite of {@link #sequenceFx}.
22605      * @return {Object} this
22606      */
22607     syncFx: function() {
22608         Ext.fx.Manager.setFxDefaults(this.id, {
22609             concurrent: true
22610         });
22611         return this;
22612     },
22613
22614     /**
22615      * Ensures that all effects queued after sequenceFx is called on this object are
22616      * run in sequence.  This is the opposite of {@link #syncFx}.
22617      * @return {Object} this
22618      */
22619     sequenceFx: function() {
22620         Ext.fx.Manager.setFxDefaults(this.id, {
22621             concurrent: false
22622         });
22623         return this;
22624     },
22625
22626     /**
22627      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
22628      * @alias Ext.util.Animate#getActiveAnimation
22629      * @method
22630      */
22631     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
22632
22633     /**
22634      * Returns the current animation if this object has any effects actively running or queued, else returns false.
22635      * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
22636      */
22637     getActiveAnimation: function() {
22638         return Ext.fx.Manager.getActiveAnimation(this.id);
22639     }
22640 }, function(){
22641     // Apply Animate mixin manually until Element is defined in the proper 4.x way
22642     Ext.applyIf(Ext.Element.prototype, this.prototype);
22643     // We need to call this again so the animation methods get copied over to CE
22644     Ext.CompositeElementLite.importElementMethods();
22645 });
22646 /**
22647  * @class Ext.state.Provider
22648  * <p>Abstract base class for state provider implementations. The provider is responsible
22649  * for setting values  and extracting values to/from the underlying storage source. The 
22650  * storage source can vary and the details should be implemented in a subclass. For example
22651  * a provider could use a server side database or the browser localstorage where supported.</p>
22652  *
22653  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
22654  * dates and defines the Provider interface. By default these methods put the value and the
22655  * type information into a delimited string that can be stored. These should be overridden in 
22656  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
22657  */
22658 Ext.define('Ext.state.Provider', {
22659     mixins: {
22660         observable: 'Ext.util.Observable'
22661     },
22662     
22663     /**
22664      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
22665      * Defaults to <tt>'ext-'</tt>
22666      */
22667     prefix: 'ext-',
22668     
22669     constructor : function(config){
22670         config = config || {};
22671         var me = this;
22672         Ext.apply(me, config);
22673         /**
22674          * @event statechange
22675          * Fires when a state change occurs.
22676          * @param {Ext.state.Provider} this This state provider
22677          * @param {String} key The state key which was changed
22678          * @param {String} value The encoded value for the state
22679          */
22680         me.addEvents("statechange");
22681         me.state = {};
22682         me.mixins.observable.constructor.call(me);
22683     },
22684     
22685     /**
22686      * Returns the current value for a key
22687      * @param {String} name The key name
22688      * @param {Object} defaultValue A default value to return if the key's value is not found
22689      * @return {Object} The state data
22690      */
22691     get : function(name, defaultValue){
22692         return typeof this.state[name] == "undefined" ?
22693             defaultValue : this.state[name];
22694     },
22695
22696     /**
22697      * Clears a value from the state
22698      * @param {String} name The key name
22699      */
22700     clear : function(name){
22701         var me = this;
22702         delete me.state[name];
22703         me.fireEvent("statechange", me, name, null);
22704     },
22705
22706     /**
22707      * Sets the value for a key
22708      * @param {String} name The key name
22709      * @param {Object} value The value to set
22710      */
22711     set : function(name, value){
22712         var me = this;
22713         me.state[name] = value;
22714         me.fireEvent("statechange", me, name, value);
22715     },
22716
22717     /**
22718      * Decodes a string previously encoded with {@link #encodeValue}.
22719      * @param {String} value The value to decode
22720      * @return {Object} The decoded value
22721      */
22722     decodeValue : function(value){
22723
22724         // a -> Array
22725         // n -> Number
22726         // d -> Date
22727         // b -> Boolean
22728         // s -> String
22729         // o -> Object
22730         // -> Empty (null)
22731
22732         var me = this,
22733             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
22734             matches = re.exec(unescape(value)),
22735             all,
22736             type,
22737             value,
22738             keyValue;
22739             
22740         if(!matches || !matches[1]){
22741             return; // non state
22742         }
22743         
22744         type = matches[1];
22745         value = matches[2];
22746         switch (type) {
22747             case 'e':
22748                 return null;
22749             case 'n':
22750                 return parseFloat(value);
22751             case 'd':
22752                 return new Date(Date.parse(value));
22753             case 'b':
22754                 return (value == '1');
22755             case 'a':
22756                 all = [];
22757                 if(value != ''){
22758                     Ext.each(value.split('^'), function(val){
22759                         all.push(me.decodeValue(val));
22760                     }, me);
22761                 }
22762                 return all;
22763            case 'o':
22764                 all = {};
22765                 if(value != ''){
22766                     Ext.each(value.split('^'), function(val){
22767                         keyValue = val.split('=');
22768                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
22769                     }, me);
22770                 }
22771                 return all;
22772            default:
22773                 return value;
22774         }
22775     },
22776
22777     /**
22778      * Encodes a value including type information.  Decode with {@link #decodeValue}.
22779      * @param {Object} value The value to encode
22780      * @return {String} The encoded value
22781      */
22782     encodeValue : function(value){
22783         var flat = '',
22784             i = 0,
22785             enc,
22786             len,
22787             key;
22788             
22789         if (value == null) {
22790             return 'e:1';    
22791         } else if(typeof value == 'number') {
22792             enc = 'n:' + value;
22793         } else if(typeof value == 'boolean') {
22794             enc = 'b:' + (value ? '1' : '0');
22795         } else if(Ext.isDate(value)) {
22796             enc = 'd:' + value.toGMTString();
22797         } else if(Ext.isArray(value)) {
22798             for (len = value.length; i < len; i++) {
22799                 flat += this.encodeValue(value[i]);
22800                 if (i != len - 1) {
22801                     flat += '^';
22802                 }
22803             }
22804             enc = 'a:' + flat;
22805         } else if (typeof value == 'object') {
22806             for (key in value) {
22807                 if (typeof value[key] != 'function' && value[key] !== undefined) {
22808                     flat += key + '=' + this.encodeValue(value[key]) + '^';
22809                 }
22810             }
22811             enc = 'o:' + flat.substring(0, flat.length-1);
22812         } else {
22813             enc = 's:' + value;
22814         }
22815         return escape(enc);
22816     }
22817 });
22818 /**
22819  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
22820  * Ext.container.Container on the document with a similar syntax to a CSS selector.
22821  *
22822  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
22823  *
22824  * - `component` or `.component`
22825  * - `gridpanel` or `.gridpanel`
22826  *
22827  * An itemId or id must be prefixed with a #
22828  *
22829  * - `#myContainer`
22830  *
22831  * Attributes must be wrapped in brackets
22832  *
22833  * - `component[autoScroll]`
22834  * - `panel[title="Test"]`
22835  *
22836  * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
22837  * the candidate Component will be included in the query:
22838  *
22839  *     var disabledFields = myFormPanel.query("{isDisabled()}");
22840  *
22841  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
22842  *
22843  *     // Function receives array and returns a filtered array.
22844  *     Ext.ComponentQuery.pseudos.invalid = function(items) {
22845  *         var i = 0, l = items.length, c, result = [];
22846  *         for (; i < l; i++) {
22847  *             if (!(c = items[i]).isValid()) {
22848  *                 result.push(c);
22849  *             }
22850  *         }
22851  *         return result;
22852  *     };
22853  *      
22854  *     var invalidFields = myFormPanel.query('field:invalid');
22855  *     if (invalidFields.length) {
22856  *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
22857  *         for (var i = 0, l = invalidFields.length; i < l; i++) {
22858  *             invalidFields[i].getEl().frame("red");
22859  *         }
22860  *     }
22861  *
22862  * Default pseudos include:
22863  *
22864  * - not
22865  * - last
22866  *
22867  * Queries return an array of components.
22868  * Here are some example queries.
22869  *
22870  *     // retrieve all Ext.Panels in the document by xtype
22871  *     var panelsArray = Ext.ComponentQuery.query('panel');
22872  *
22873  *     // retrieve all Ext.Panels within the container with an id myCt
22874  *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
22875  *
22876  *     // retrieve all direct children which are Ext.Panels within myCt
22877  *     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
22878  *
22879  *     // retrieve all grids and trees
22880  *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
22881  *
22882  * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
22883  * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
22884  * {@link Ext.Component#up}.
22885  */
22886 Ext.define('Ext.ComponentQuery', {
22887     singleton: true,
22888     uses: ['Ext.ComponentManager']
22889 }, function() {
22890
22891     var cq = this,
22892
22893         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
22894         // as a member on each item in the passed array.
22895         filterFnPattern = [
22896             'var r = [],',
22897                 'i = 0,',
22898                 'it = items,',
22899                 'l = it.length,',
22900                 'c;',
22901             'for (; i < l; i++) {',
22902                 'c = it[i];',
22903                 'if (c.{0}) {',
22904                    'r.push(c);',
22905                 '}',
22906             '}',
22907             'return r;'
22908         ].join(''),
22909
22910         filterItems = function(items, operation) {
22911             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
22912             // The operation's method loops over each item in the candidate array and
22913             // returns an array of items which match its criteria
22914             return operation.method.apply(this, [ items ].concat(operation.args));
22915         },
22916
22917         getItems = function(items, mode) {
22918             var result = [],
22919                 i = 0,
22920                 length = items.length,
22921                 candidate,
22922                 deep = mode !== '>';
22923                 
22924             for (; i < length; i++) {
22925                 candidate = items[i];
22926                 if (candidate.getRefItems) {
22927                     result = result.concat(candidate.getRefItems(deep));
22928                 }
22929             }
22930             return result;
22931         },
22932
22933         getAncestors = function(items) {
22934             var result = [],
22935                 i = 0,
22936                 length = items.length,
22937                 candidate;
22938             for (; i < length; i++) {
22939                 candidate = items[i];
22940                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
22941                     result.push(candidate);
22942                 }
22943             }
22944             return result;
22945         },
22946
22947         // Filters the passed candidate array and returns only items which match the passed xtype
22948         filterByXType = function(items, xtype, shallow) {
22949             if (xtype === '*') {
22950                 return items.slice();
22951             }
22952             else {
22953                 var result = [],
22954                     i = 0,
22955                     length = items.length,
22956                     candidate;
22957                 for (; i < length; i++) {
22958                     candidate = items[i];
22959                     if (candidate.isXType(xtype, shallow)) {
22960                         result.push(candidate);
22961                     }
22962                 }
22963                 return result;
22964             }
22965         },
22966
22967         // Filters the passed candidate array and returns only items which have the passed className
22968         filterByClassName = function(items, className) {
22969             var EA = Ext.Array,
22970                 result = [],
22971                 i = 0,
22972                 length = items.length,
22973                 candidate;
22974             for (; i < length; i++) {
22975                 candidate = items[i];
22976                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
22977                     result.push(candidate);
22978                 }
22979             }
22980             return result;
22981         },
22982
22983         // Filters the passed candidate array and returns only items which have the specified property match
22984         filterByAttribute = function(items, property, operator, value) {
22985             var result = [],
22986                 i = 0,
22987                 length = items.length,
22988                 candidate;
22989             for (; i < length; i++) {
22990                 candidate = items[i];
22991                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
22992                     result.push(candidate);
22993                 }
22994             }
22995             return result;
22996         },
22997
22998         // Filters the passed candidate array and returns only items which have the specified itemId or id
22999         filterById = function(items, id) {
23000             var result = [],
23001                 i = 0,
23002                 length = items.length,
23003                 candidate;
23004             for (; i < length; i++) {
23005                 candidate = items[i];
23006                 if (candidate.getItemId() === id) {
23007                     result.push(candidate);
23008                 }
23009             }
23010             return result;
23011         },
23012
23013         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
23014         filterByPseudo = function(items, name, value) {
23015             return cq.pseudos[name](items, value);
23016         },
23017
23018         // Determines leading mode
23019         // > for direct child, and ^ to switch to ownerCt axis
23020         modeRe = /^(\s?([>\^])\s?|\s|$)/,
23021
23022         // Matches a token with possibly (true|false) appended for the "shallow" parameter
23023         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
23024
23025         matchers = [{
23026             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
23027             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
23028             method: filterByXType
23029         },{
23030             // checks for [attribute=value]
23031             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
23032             method: filterByAttribute
23033         }, {
23034             // checks for #cmpItemId
23035             re: /^#([\w\-]+)/,
23036             method: filterById
23037         }, {
23038             // checks for :<pseudo_class>(<selector>)
23039             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
23040             method: filterByPseudo
23041         }, {
23042             // checks for {<member_expression>}
23043             re: /^(?:\{([^\}]+)\})/,
23044             method: filterFnPattern
23045         }];
23046
23047     // @class Ext.ComponentQuery.Query
23048     // This internal class is completely hidden in documentation.
23049     cq.Query = Ext.extend(Object, {
23050         constructor: function(cfg) {
23051             cfg = cfg || {};
23052             Ext.apply(this, cfg);
23053         },
23054
23055         // Executes this Query upon the selected root.
23056         // The root provides the initial source of candidate Component matches which are progressively
23057         // filtered by iterating through this Query's operations cache.
23058         // If no root is provided, all registered Components are searched via the ComponentManager.
23059         // root may be a Container who's descendant Components are filtered
23060         // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
23061         // docked items within a Panel.
23062         // root may be an array of candidate Components to filter using this Query.
23063         execute : function(root) {
23064             var operations = this.operations,
23065                 i = 0,
23066                 length = operations.length,
23067                 operation,
23068                 workingItems;
23069
23070             // no root, use all Components in the document
23071             if (!root) {
23072                 workingItems = Ext.ComponentManager.all.getArray();
23073             }
23074             // Root is a candidate Array
23075             else if (Ext.isArray(root)) {
23076                 workingItems = root;
23077             }
23078
23079             // We are going to loop over our operations and take care of them
23080             // one by one.
23081             for (; i < length; i++) {
23082                 operation = operations[i];
23083
23084                 // The mode operation requires some custom handling.
23085                 // All other operations essentially filter down our current
23086                 // working items, while mode replaces our current working
23087                 // items by getting children from each one of our current
23088                 // working items. The type of mode determines the type of
23089                 // children we get. (e.g. > only gets direct children)
23090                 if (operation.mode === '^') {
23091                     workingItems = getAncestors(workingItems || [root]);
23092                 }
23093                 else if (operation.mode) {
23094                     workingItems = getItems(workingItems || [root], operation.mode);
23095                 }
23096                 else {
23097                     workingItems = filterItems(workingItems || getItems([root]), operation);
23098                 }
23099
23100                 // If this is the last operation, it means our current working
23101                 // items are the final matched items. Thus return them!
23102                 if (i === length -1) {
23103                     return workingItems;
23104                 }
23105             }
23106             return [];
23107         },
23108
23109         is: function(component) {
23110             var operations = this.operations,
23111                 components = Ext.isArray(component) ? component : [component],
23112                 originalLength = components.length,
23113                 lastOperation = operations[operations.length-1],
23114                 ln, i;
23115
23116             components = filterItems(components, lastOperation);
23117             if (components.length === originalLength) {
23118                 if (operations.length > 1) {
23119                     for (i = 0, ln = components.length; i < ln; i++) {
23120                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
23121                             return false;
23122                         }
23123                     }
23124                 }
23125                 return true;
23126             }
23127             return false;
23128         }
23129     });
23130
23131     Ext.apply(this, {
23132
23133         // private cache of selectors and matching ComponentQuery.Query objects
23134         cache: {},
23135
23136         // private cache of pseudo class filter functions
23137         pseudos: {
23138             not: function(components, selector){
23139                 var CQ = Ext.ComponentQuery,
23140                     i = 0,
23141                     length = components.length,
23142                     results = [],
23143                     index = -1,
23144                     component;
23145                 
23146                 for(; i < length; ++i) {
23147                     component = components[i];
23148                     if (!CQ.is(component, selector)) {
23149                         results[++index] = component;
23150                     }
23151                 }
23152                 return results;
23153             },
23154             last: function(components) {
23155                 return components[components.length - 1];
23156             }
23157         },
23158
23159         /**
23160          * Returns an array of matched Components from within the passed root object.
23161          *
23162          * This method filters returned Components in a similar way to how CSS selector based DOM
23163          * queries work using a textual selector string.
23164          *
23165          * See class summary for details.
23166          *
23167          * @param {String} selector The selector string to filter returned Components
23168          * @param {Ext.container.Container} root The Container within which to perform the query.
23169          * If omitted, all Components within the document are included in the search.
23170          * 
23171          * This parameter may also be an array of Components to filter according to the selector.</p>
23172          * @returns {Ext.Component[]} The matched Components.
23173          * 
23174          * @member Ext.ComponentQuery
23175          */
23176         query: function(selector, root) {
23177             var selectors = selector.split(','),
23178                 length = selectors.length,
23179                 i = 0,
23180                 results = [],
23181                 noDupResults = [], 
23182                 dupMatcher = {}, 
23183                 query, resultsLn, cmp;
23184
23185             for (; i < length; i++) {
23186                 selector = Ext.String.trim(selectors[i]);
23187                 query = this.cache[selector];
23188                 if (!query) {
23189                     this.cache[selector] = query = this.parse(selector);
23190                 }
23191                 results = results.concat(query.execute(root));
23192             }
23193
23194             // multiple selectors, potential to find duplicates
23195             // lets filter them out.
23196             if (length > 1) {
23197                 resultsLn = results.length;
23198                 for (i = 0; i < resultsLn; i++) {
23199                     cmp = results[i];
23200                     if (!dupMatcher[cmp.id]) {
23201                         noDupResults.push(cmp);
23202                         dupMatcher[cmp.id] = true;
23203                     }
23204                 }
23205                 results = noDupResults;
23206             }
23207             return results;
23208         },
23209
23210         /**
23211          * Tests whether the passed Component matches the selector string.
23212          * @param {Ext.Component} component The Component to test
23213          * @param {String} selector The selector string to test against.
23214          * @return {Boolean} True if the Component matches the selector.
23215          * @member Ext.ComponentQuery
23216          */
23217         is: function(component, selector) {
23218             if (!selector) {
23219                 return true;
23220             }
23221             var query = this.cache[selector];
23222             if (!query) {
23223                 this.cache[selector] = query = this.parse(selector);
23224             }
23225             return query.is(component);
23226         },
23227
23228         parse: function(selector) {
23229             var operations = [],
23230                 length = matchers.length,
23231                 lastSelector,
23232                 tokenMatch,
23233                 matchedChar,
23234                 modeMatch,
23235                 selectorMatch,
23236                 i, matcher, method;
23237
23238             // We are going to parse the beginning of the selector over and
23239             // over again, slicing off the selector any portions we converted into an
23240             // operation, until it is an empty string.
23241             while (selector && lastSelector !== selector) {
23242                 lastSelector = selector;
23243
23244                 // First we check if we are dealing with a token like #, * or an xtype
23245                 tokenMatch = selector.match(tokenRe);
23246
23247                 if (tokenMatch) {
23248                     matchedChar = tokenMatch[1];
23249
23250                     // If the token is prefixed with a # we push a filterById operation to our stack
23251                     if (matchedChar === '#') {
23252                         operations.push({
23253                             method: filterById,
23254                             args: [Ext.String.trim(tokenMatch[2])]
23255                         });
23256                     }
23257                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
23258                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
23259                     else if (matchedChar === '.') {
23260                         operations.push({
23261                             method: filterByClassName,
23262                             args: [Ext.String.trim(tokenMatch[2])]
23263                         });
23264                     }
23265                     // If the token is a * or an xtype string, we push a filterByXType
23266                     // operation to the stack.
23267                     else {
23268                         operations.push({
23269                             method: filterByXType,
23270                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
23271                         });
23272                     }
23273
23274                     // Now we slice of the part we just converted into an operation
23275                     selector = selector.replace(tokenMatch[0], '');
23276                 }
23277
23278                 // If the next part of the query is not a space or > or ^, it means we
23279                 // are going to check for more things that our current selection
23280                 // has to comply to.
23281                 while (!(modeMatch = selector.match(modeRe))) {
23282                     // Lets loop over each type of matcher and execute it
23283                     // on our current selector.
23284                     for (i = 0; selector && i < length; i++) {
23285                         matcher = matchers[i];
23286                         selectorMatch = selector.match(matcher.re);
23287                         method = matcher.method;
23288
23289                         // If we have a match, add an operation with the method
23290                         // associated with this matcher, and pass the regular
23291                         // expression matches are arguments to the operation.
23292                         if (selectorMatch) {
23293                             operations.push({
23294                                 method: Ext.isString(matcher.method)
23295                                     // Turn a string method into a function by formatting the string with our selector matche expression
23296                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
23297                                     // Every expression may be different in different selectors.
23298                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
23299                                     : matcher.method,
23300                                 args: selectorMatch.slice(1)
23301                             });
23302                             selector = selector.replace(selectorMatch[0], '');
23303                             break; // Break on match
23304                         }
23305                         // Exhausted all matches: It's an error
23306                         if (i === (length - 1)) {
23307                             Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
23308                         }
23309                     }
23310                 }
23311
23312                 // Now we are going to check for a mode change. This means a space
23313                 // or a > to determine if we are going to select all the children
23314                 // of the currently matched items, or a ^ if we are going to use the
23315                 // ownerCt axis as the candidate source.
23316                 if (modeMatch[1]) { // Assignment, and test for truthiness!
23317                     operations.push({
23318                         mode: modeMatch[2]||modeMatch[1]
23319                     });
23320                     selector = selector.replace(modeMatch[0], '');
23321                 }
23322             }
23323
23324             //  Now that we have all our operations in an array, we are going
23325             // to create a new Query using these operations.
23326             return new cq.Query({
23327                 operations: operations
23328             });
23329         }
23330     });
23331 });
23332 /**
23333  * @class Ext.util.HashMap
23334  * <p>
23335  * Represents a collection of a set of key and value pairs. Each key in the HashMap
23336  * must be unique, the same key cannot exist twice. Access to items is provided via
23337  * the key only. Sample usage:
23338  * <pre><code>
23339 var map = new Ext.util.HashMap();
23340 map.add('key1', 1);
23341 map.add('key2', 2);
23342 map.add('key3', 3);
23343
23344 map.each(function(key, value, length){
23345     console.log(key, value, length);
23346 });
23347  * </code></pre>
23348  * </p>
23349  *
23350  * <p>The HashMap is an unordered class,
23351  * there is no guarantee when iterating over the items that they will be in any particular
23352  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
23353  * </p>
23354  */
23355 Ext.define('Ext.util.HashMap', {
23356     mixins: {
23357         observable: 'Ext.util.Observable'
23358     },
23359
23360     /**
23361      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
23362      * A default is provided that returns the <b>id</b> property on the object. This function is only used
23363      * if the add method is called with a single argument.
23364      */
23365
23366     /**
23367      * Creates new HashMap.
23368      * @param {Object} config (optional) Config object.
23369      */
23370     constructor: function(config) {
23371         config = config || {};
23372         
23373         var me = this,
23374             keyFn = config.keyFn;
23375
23376         me.addEvents(
23377             /**
23378              * @event add
23379              * Fires when a new item is added to the hash
23380              * @param {Ext.util.HashMap} this.
23381              * @param {String} key The key of the added item.
23382              * @param {Object} value The value of the added item.
23383              */
23384             'add',
23385             /**
23386              * @event clear
23387              * Fires when the hash is cleared.
23388              * @param {Ext.util.HashMap} this.
23389              */
23390             'clear',
23391             /**
23392              * @event remove
23393              * Fires when an item is removed from the hash.
23394              * @param {Ext.util.HashMap} this.
23395              * @param {String} key The key of the removed item.
23396              * @param {Object} value The value of the removed item.
23397              */
23398             'remove',
23399             /**
23400              * @event replace
23401              * Fires when an item is replaced in the hash.
23402              * @param {Ext.util.HashMap} this.
23403              * @param {String} key The key of the replaced item.
23404              * @param {Object} value The new value for the item.
23405              * @param {Object} old The old value for the item.
23406              */
23407             'replace'
23408         );
23409
23410         me.mixins.observable.constructor.call(me, config);
23411         me.clear(true);
23412         
23413         if (keyFn) {
23414             me.getKey = keyFn;
23415         }
23416     },
23417
23418     /**
23419      * Gets the number of items in the hash.
23420      * @return {Number} The number of items in the hash.
23421      */
23422     getCount: function() {
23423         return this.length;
23424     },
23425
23426     /**
23427      * Implementation for being able to extract the key from an object if only
23428      * a single argument is passed.
23429      * @private
23430      * @param {String} key The key
23431      * @param {Object} value The value
23432      * @return {Array} [key, value]
23433      */
23434     getData: function(key, value) {
23435         // if we have no value, it means we need to get the key from the object
23436         if (value === undefined) {
23437             value = key;
23438             key = this.getKey(value);
23439         }
23440
23441         return [key, value];
23442     },
23443
23444     /**
23445      * Extracts the key from an object. This is a default implementation, it may be overridden
23446      * @param {Object} o The object to get the key from
23447      * @return {String} The key to use.
23448      */
23449     getKey: function(o) {
23450         return o.id;
23451     },
23452
23453     /**
23454      * Adds an item to the collection. Fires the {@link #add} event when complete.
23455      * @param {String} key <p>The key to associate with the item, or the new item.</p>
23456      * <p>If a {@link #getKey} implementation was specified for this HashMap,
23457      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
23458      * the HashMap will be able to <i>derive</i> the key for the new item.
23459      * In this case just pass the new item in this parameter.</p>
23460      * @param {Object} o The item to add.
23461      * @return {Object} The item added.
23462      */
23463     add: function(key, value) {
23464         var me = this,
23465             data;
23466
23467         if (arguments.length === 1) {
23468             value = key;
23469             key = me.getKey(value);
23470         }
23471
23472         if (me.containsKey(key)) {
23473             return me.replace(key, value);
23474         }
23475
23476         data = me.getData(key, value);
23477         key = data[0];
23478         value = data[1];
23479         me.map[key] = value;
23480         ++me.length;
23481         me.fireEvent('add', me, key, value);
23482         return value;
23483     },
23484
23485     /**
23486      * Replaces an item in the hash. If the key doesn't exist, the
23487      * {@link #add} method will be used.
23488      * @param {String} key The key of the item.
23489      * @param {Object} value The new value for the item.
23490      * @return {Object} The new value of the item.
23491      */
23492     replace: function(key, value) {
23493         var me = this,
23494             map = me.map,
23495             old;
23496
23497         if (!me.containsKey(key)) {
23498             me.add(key, value);
23499         }
23500         old = map[key];
23501         map[key] = value;
23502         me.fireEvent('replace', me, key, value, old);
23503         return value;
23504     },
23505
23506     /**
23507      * Remove an item from the hash.
23508      * @param {Object} o The value of the item to remove.
23509      * @return {Boolean} True if the item was successfully removed.
23510      */
23511     remove: function(o) {
23512         var key = this.findKey(o);
23513         if (key !== undefined) {
23514             return this.removeAtKey(key);
23515         }
23516         return false;
23517     },
23518
23519     /**
23520      * Remove an item from the hash.
23521      * @param {String} key The key to remove.
23522      * @return {Boolean} True if the item was successfully removed.
23523      */
23524     removeAtKey: function(key) {
23525         var me = this,
23526             value;
23527
23528         if (me.containsKey(key)) {
23529             value = me.map[key];
23530             delete me.map[key];
23531             --me.length;
23532             me.fireEvent('remove', me, key, value);
23533             return true;
23534         }
23535         return false;
23536     },
23537
23538     /**
23539      * Retrieves an item with a particular key.
23540      * @param {String} key The key to lookup.
23541      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
23542      */
23543     get: function(key) {
23544         return this.map[key];
23545     },
23546
23547     /**
23548      * Removes all items from the hash.
23549      * @return {Ext.util.HashMap} this
23550      */
23551     clear: function(/* private */ initial) {
23552         var me = this;
23553         me.map = {};
23554         me.length = 0;
23555         if (initial !== true) {
23556             me.fireEvent('clear', me);
23557         }
23558         return me;
23559     },
23560
23561     /**
23562      * Checks whether a key exists in the hash.
23563      * @param {String} key The key to check for.
23564      * @return {Boolean} True if they key exists in the hash.
23565      */
23566     containsKey: function(key) {
23567         return this.map[key] !== undefined;
23568     },
23569
23570     /**
23571      * Checks whether a value exists in the hash.
23572      * @param {Object} value The value to check for.
23573      * @return {Boolean} True if the value exists in the dictionary.
23574      */
23575     contains: function(value) {
23576         return this.containsKey(this.findKey(value));
23577     },
23578
23579     /**
23580      * Return all of the keys in the hash.
23581      * @return {Array} An array of keys.
23582      */
23583     getKeys: function() {
23584         return this.getArray(true);
23585     },
23586
23587     /**
23588      * Return all of the values in the hash.
23589      * @return {Array} An array of values.
23590      */
23591     getValues: function() {
23592         return this.getArray(false);
23593     },
23594
23595     /**
23596      * Gets either the keys/values in an array from the hash.
23597      * @private
23598      * @param {Boolean} isKey True to extract the keys, otherwise, the value
23599      * @return {Array} An array of either keys/values from the hash.
23600      */
23601     getArray: function(isKey) {
23602         var arr = [],
23603             key,
23604             map = this.map;
23605         for (key in map) {
23606             if (map.hasOwnProperty(key)) {
23607                 arr.push(isKey ? key: map[key]);
23608             }
23609         }
23610         return arr;
23611     },
23612
23613     /**
23614      * Executes the specified function once for each item in the hash.
23615      * Returning false from the function will cease iteration.
23616      *
23617      * The paramaters passed to the function are:
23618      * <div class="mdetail-params"><ul>
23619      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
23620      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
23621      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
23622      * </ul></div>
23623      * @param {Function} fn The function to execute.
23624      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
23625      * @return {Ext.util.HashMap} this
23626      */
23627     each: function(fn, scope) {
23628         // copy items so they may be removed during iteration.
23629         var items = Ext.apply({}, this.map),
23630             key,
23631             length = this.length;
23632
23633         scope = scope || this;
23634         for (key in items) {
23635             if (items.hasOwnProperty(key)) {
23636                 if (fn.call(scope, key, items[key], length) === false) {
23637                     break;
23638                 }
23639             }
23640         }
23641         return this;
23642     },
23643
23644     /**
23645      * Performs a shallow copy on this hash.
23646      * @return {Ext.util.HashMap} The new hash object.
23647      */
23648     clone: function() {
23649         var hash = new this.self(),
23650             map = this.map,
23651             key;
23652
23653         hash.suspendEvents();
23654         for (key in map) {
23655             if (map.hasOwnProperty(key)) {
23656                 hash.add(key, map[key]);
23657             }
23658         }
23659         hash.resumeEvents();
23660         return hash;
23661     },
23662
23663     /**
23664      * @private
23665      * Find the key for a value.
23666      * @param {Object} value The value to find.
23667      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
23668      */
23669     findKey: function(value) {
23670         var key,
23671             map = this.map;
23672
23673         for (key in map) {
23674             if (map.hasOwnProperty(key) && map[key] === value) {
23675                 return key;
23676             }
23677         }
23678         return undefined;
23679     }
23680 });
23681
23682 /**
23683  * @class Ext.state.Manager
23684  * This is the global state manager. By default all components that are "state aware" check this class
23685  * for state information if you don't pass them a custom state provider. In order for this class
23686  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
23687  <pre><code>
23688 // in your initialization function
23689 init : function(){
23690    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
23691    var win = new Window(...);
23692    win.restoreState();
23693 }
23694  </code></pre>
23695  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
23696  * there is a common interface that can be used without needing to refer to a specific provider instance
23697  * in every component.
23698  * @singleton
23699  * @docauthor Evan Trimboli <evan@sencha.com>
23700  */
23701 Ext.define('Ext.state.Manager', {
23702     singleton: true,
23703     requires: ['Ext.state.Provider'],
23704     constructor: function() {
23705         this.provider = Ext.create('Ext.state.Provider');
23706     },
23707     
23708     
23709     /**
23710      * Configures the default state provider for your application
23711      * @param {Ext.state.Provider} stateProvider The state provider to set
23712      */
23713     setProvider : function(stateProvider){
23714         this.provider = stateProvider;
23715     },
23716
23717     /**
23718      * Returns the current value for a key
23719      * @param {String} name The key name
23720      * @param {Object} defaultValue The default value to return if the key lookup does not match
23721      * @return {Object} The state data
23722      */
23723     get : function(key, defaultValue){
23724         return this.provider.get(key, defaultValue);
23725     },
23726
23727     /**
23728      * Sets the value for a key
23729      * @param {String} name The key name
23730      * @param {Object} value The state data
23731      */
23732      set : function(key, value){
23733         this.provider.set(key, value);
23734     },
23735
23736     /**
23737      * Clears a value from the state
23738      * @param {String} name The key name
23739      */
23740     clear : function(key){
23741         this.provider.clear(key);
23742     },
23743
23744     /**
23745      * Gets the currently configured state provider
23746      * @return {Ext.state.Provider} The state provider
23747      */
23748     getProvider : function(){
23749         return this.provider;
23750     }
23751 });
23752 /**
23753  * @class Ext.state.Stateful
23754  * A mixin for being able to save the state of an object to an underlying
23755  * {@link Ext.state.Provider}.
23756  */
23757 Ext.define('Ext.state.Stateful', {
23758
23759     /* Begin Definitions */
23760
23761    mixins: {
23762         observable: 'Ext.util.Observable'
23763     },
23764
23765     requires: ['Ext.state.Manager'],
23766
23767     /* End Definitions */
23768
23769     /**
23770      * @cfg {Boolean} stateful
23771      * <p>A flag which causes the object to attempt to restore the state of
23772      * internal properties from a saved state on startup. The object must have
23773      * a <code>{@link #stateId}</code> for state to be managed.
23774      * Auto-generated ids are not guaranteed to be stable across page loads and
23775      * cannot be relied upon to save and restore the same state for a object.<p>
23776      * <p>For state saving to work, the state manager's provider must have been
23777      * set to an implementation of {@link Ext.state.Provider} which overrides the
23778      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
23779      * methods to save and recall name/value pairs. A built-in implementation,
23780      * {@link Ext.state.CookieProvider} is available.</p>
23781      * <p>To set the state provider for the current page:</p>
23782      * <pre><code>
23783 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
23784     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
23785 }));
23786      * </code></pre>
23787      * <p>A stateful object attempts to save state when one of the events
23788      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
23789      * <p>To save state, a stateful object first serializes its state by
23790      * calling <b><code>{@link #getState}</code></b>. By default, this function does
23791      * nothing. The developer must provide an implementation which returns an
23792      * object hash which represents the restorable state of the object.</p>
23793      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
23794      * which uses the configured {@link Ext.state.Provider} to save the object
23795      * keyed by the <code>{@link #stateId}</code>.</p>
23796      * <p>During construction, a stateful object attempts to <i>restore</i>
23797      * its state by calling {@link Ext.state.Manager#get} passing the
23798      * <code>{@link #stateId}</code></p>
23799      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
23800      * The default implementation of <code>{@link #applyState}</code> simply copies
23801      * properties into the object, but a developer may override this to support
23802      * more behaviour.</p>
23803      * <p>You can perform extra processing on state save and restore by attaching
23804      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
23805      * {@link #beforestatesave} and {@link #statesave} events.</p>
23806      */
23807     stateful: true,
23808
23809     /**
23810      * @cfg {String} stateId
23811      * The unique id for this object to use for state management purposes.
23812      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
23813      */
23814
23815     /**
23816      * @cfg {String[]} stateEvents
23817      * <p>An array of events that, when fired, should trigger this object to
23818      * save its state. Defaults to none. <code>stateEvents</code> may be any type
23819      * of event supported by this object, including browser or custom events
23820      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
23821      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
23822      * restoring object state.</p>
23823      */
23824
23825     /**
23826      * @cfg {Number} saveDelay
23827      * A buffer to be applied if many state events are fired within a short period.
23828      */
23829     saveDelay: 100,
23830
23831     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
23832
23833     constructor: function(config) {
23834         var me = this;
23835
23836         config = config || {};
23837         if (Ext.isDefined(config.stateful)) {
23838             me.stateful = config.stateful;
23839         }
23840         if (Ext.isDefined(config.saveDelay)) {
23841             me.saveDelay = config.saveDelay;
23842         }
23843         me.stateId = me.stateId || config.stateId;
23844
23845         if (!me.stateEvents) {
23846             me.stateEvents = [];
23847         }
23848         if (config.stateEvents) {
23849             me.stateEvents.concat(config.stateEvents);
23850         }
23851         this.addEvents(
23852             /**
23853              * @event beforestaterestore
23854              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
23855              * @param {Ext.state.Stateful} this
23856              * @param {Object} state The hash of state values returned from the StateProvider. If this
23857              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
23858              * that simply copies property values into this object. The method maybe overriden to
23859              * provide custom state restoration.
23860              */
23861             'beforestaterestore',
23862
23863             /**
23864              * @event staterestore
23865              * Fires after the state of the object is restored.
23866              * @param {Ext.state.Stateful} this
23867              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
23868              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
23869              * object. The method maybe overriden to provide custom state restoration.
23870              */
23871             'staterestore',
23872
23873             /**
23874              * @event beforestatesave
23875              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
23876              * @param {Ext.state.Stateful} this
23877              * @param {Object} state The hash of state values. This is determined by calling
23878              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23879              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23880              * has a null implementation.
23881              */
23882             'beforestatesave',
23883
23884             /**
23885              * @event statesave
23886              * Fires after the state of the object is saved to the configured state provider.
23887              * @param {Ext.state.Stateful} this
23888              * @param {Object} state The hash of state values. This is determined by calling
23889              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23890              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23891              * has a null implementation.
23892              */
23893             'statesave'
23894         );
23895         me.mixins.observable.constructor.call(me);
23896         if (me.stateful !== false) {
23897             me.initStateEvents();
23898             me.initState();
23899         }
23900     },
23901
23902     /**
23903      * Initializes any state events for this object.
23904      * @private
23905      */
23906     initStateEvents: function() {
23907         this.addStateEvents(this.stateEvents);
23908     },
23909
23910     /**
23911      * Add events that will trigger the state to be saved.
23912      * @param {String/String[]} events The event name or an array of event names.
23913      */
23914     addStateEvents: function(events){
23915         if (!Ext.isArray(events)) {
23916             events = [events];
23917         }
23918
23919         var me = this,
23920             i = 0,
23921             len = events.length;
23922
23923         for (; i < len; ++i) {
23924             me.on(events[i], me.onStateChange, me);
23925         }
23926     },
23927
23928     /**
23929      * This method is called when any of the {@link #stateEvents} are fired.
23930      * @private
23931      */
23932     onStateChange: function(){
23933         var me = this,
23934             delay = me.saveDelay;
23935
23936         if (delay > 0) {
23937             if (!me.stateTask) {
23938                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
23939             }
23940             me.stateTask.delay(me.saveDelay);
23941         } else {
23942             me.saveState();
23943         }
23944     },
23945
23946     /**
23947      * Saves the state of the object to the persistence store.
23948      * @private
23949      */
23950     saveState: function() {
23951         var me = this,
23952             id,
23953             state;
23954
23955         if (me.stateful !== false) {
23956             id = me.getStateId();
23957             if (id) {
23958                 state = me.getState();
23959                 if (me.fireEvent('beforestatesave', me, state) !== false) {
23960                     Ext.state.Manager.set(id, state);
23961                     me.fireEvent('statesave', me, state);
23962                 }
23963             }
23964         }
23965     },
23966
23967     /**
23968      * Gets the current state of the object. By default this function returns null,
23969      * it should be overridden in subclasses to implement methods for getting the state.
23970      * @return {Object} The current state
23971      */
23972     getState: function(){
23973         return null;
23974     },
23975
23976     /**
23977      * Applies the state to the object. This should be overridden in subclasses to do
23978      * more complex state operations. By default it applies the state properties onto
23979      * the current object.
23980      * @param {Object} state The state
23981      */
23982     applyState: function(state) {
23983         if (state) {
23984             Ext.apply(this, state);
23985         }
23986     },
23987
23988     /**
23989      * Gets the state id for this object.
23990      * @return {String} The state id, null if not found.
23991      */
23992     getStateId: function() {
23993         var me = this,
23994             id = me.stateId;
23995
23996         if (!id) {
23997             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
23998         }
23999         return id;
24000     },
24001
24002     /**
24003      * Initializes the state of the object upon construction.
24004      * @private
24005      */
24006     initState: function(){
24007         var me = this,
24008             id = me.getStateId(),
24009             state;
24010
24011         if (me.stateful !== false) {
24012             if (id) {
24013                 state = Ext.state.Manager.get(id);
24014                 if (state) {
24015                     state = Ext.apply({}, state);
24016                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
24017                         me.applyState(state);
24018                         me.fireEvent('staterestore', me, state);
24019                     }
24020                 }
24021             }
24022         }
24023     },
24024
24025     /**
24026      * Conditionally saves a single property from this object to the given state object.
24027      * The idea is to only save state which has changed from the initial state so that
24028      * current software settings do not override future software settings. Only those
24029      * values that are user-changed state should be saved.
24030      *
24031      * @param {String} propName The name of the property to save.
24032      * @param {Object} state The state object in to which to save the property.
24033      * @param {String} stateName (optional) The name to use for the property in state.
24034      * @return {Boolean} True if the property was saved, false if not.
24035      */
24036     savePropToState: function (propName, state, stateName) {
24037         var me = this,
24038             value = me[propName],
24039             config = me.initialConfig;
24040
24041         if (me.hasOwnProperty(propName)) {
24042             if (!config || config[propName] !== value) {
24043                 if (state) {
24044                     state[stateName || propName] = value;
24045                 }
24046                 return true;
24047             }
24048         }
24049         return false;
24050     },
24051
24052     savePropsToState: function (propNames, state) {
24053         var me = this;
24054         Ext.each(propNames, function (propName) {
24055             me.savePropToState(propName, state);
24056         });
24057         return state;
24058     },
24059
24060     /**
24061      * Destroys this stateful object.
24062      */
24063     destroy: function(){
24064         var task = this.stateTask;
24065         if (task) {
24066             task.cancel();
24067         }
24068         this.clearListeners();
24069
24070     }
24071
24072 });
24073
24074 /**
24075  * Base Manager class
24076  */
24077 Ext.define('Ext.AbstractManager', {
24078
24079     /* Begin Definitions */
24080
24081     requires: ['Ext.util.HashMap'],
24082
24083     /* End Definitions */
24084
24085     typeName: 'type',
24086
24087     constructor: function(config) {
24088         Ext.apply(this, config || {});
24089
24090         /**
24091          * @property {Ext.util.HashMap} all
24092          * Contains all of the items currently managed
24093          */
24094         this.all = Ext.create('Ext.util.HashMap');
24095
24096         this.types = {};
24097     },
24098
24099     /**
24100      * Returns an item by id.
24101      * For additional details see {@link Ext.util.HashMap#get}.
24102      * @param {String} id The id of the item
24103      * @return {Object} The item, undefined if not found.
24104      */
24105     get : function(id) {
24106         return this.all.get(id);
24107     },
24108
24109     /**
24110      * Registers an item to be managed
24111      * @param {Object} item The item to register
24112      */
24113     register: function(item) {
24114         var all = this.all,
24115             key = all.getKey(item);
24116             
24117         if (all.containsKey(key)) {
24118             Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
24119         }
24120         this.all.add(item);
24121     },
24122
24123     /**
24124      * Unregisters an item by removing it from this manager
24125      * @param {Object} item The item to unregister
24126      */
24127     unregister: function(item) {
24128         this.all.remove(item);
24129     },
24130
24131     /**
24132      * Registers a new item constructor, keyed by a type key.
24133      * @param {String} type The mnemonic string by which the class may be looked up.
24134      * @param {Function} cls The new instance class.
24135      */
24136     registerType : function(type, cls) {
24137         this.types[type] = cls;
24138         cls[this.typeName] = type;
24139     },
24140
24141     /**
24142      * Checks if an item type is registered.
24143      * @param {String} type The mnemonic string by which the class may be looked up
24144      * @return {Boolean} Whether the type is registered.
24145      */
24146     isRegistered : function(type){
24147         return this.types[type] !== undefined;
24148     },
24149
24150     /**
24151      * Creates and returns an instance of whatever this manager manages, based on the supplied type and
24152      * config object.
24153      * @param {Object} config The config object
24154      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
24155      * @return {Object} The instance of whatever this manager is managing
24156      */
24157     create: function(config, defaultType) {
24158         var type        = config[this.typeName] || config.type || defaultType,
24159             Constructor = this.types[type];
24160
24161         if (Constructor === undefined) {
24162             Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
24163         }
24164
24165         return new Constructor(config);
24166     },
24167
24168     /**
24169      * Registers a function that will be called when an item with the specified id is added to the manager.
24170      * This will happen on instantiation.
24171      * @param {String} id The item id
24172      * @param {Function} fn The callback function. Called with a single parameter, the item.
24173      * @param {Object} scope The scope (this reference) in which the callback is executed.
24174      * Defaults to the item.
24175      */
24176     onAvailable : function(id, fn, scope){
24177         var all = this.all,
24178             item;
24179         
24180         if (all.containsKey(id)) {
24181             item = all.get(id);
24182             fn.call(scope || item, item);
24183         } else {
24184             all.on('add', function(map, key, item){
24185                 if (key == id) {
24186                     fn.call(scope || item, item);
24187                     all.un('add', fn, scope);
24188                 }
24189             });
24190         }
24191     },
24192     
24193     /**
24194      * Executes the specified function once for each item in the collection.
24195      * @param {Function} fn The function to execute.
24196      * @param {String} fn.key The key of the item
24197      * @param {Number} fn.value The value of the item
24198      * @param {Number} fn.length The total number of items in the collection
24199      * @param {Boolean} fn.return False to cease iteration.
24200      * @param {Object} scope The scope to execute in. Defaults to `this`.
24201      */
24202     each: function(fn, scope){
24203         this.all.each(fn, scope || this);    
24204     },
24205     
24206     /**
24207      * Gets the number of items in the collection.
24208      * @return {Number} The number of items in the collection.
24209      */
24210     getCount: function(){
24211         return this.all.getCount();
24212     }
24213 });
24214
24215 /**
24216  * @class Ext.ComponentManager
24217  * @extends Ext.AbstractManager
24218  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
24219  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
24220  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
24221  * <p>This object also provides a registry of available Component <i>classes</i>
24222  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
24223  * The <code>xtype</code> provides a way to avoid instantiating child Components
24224  * when creating a full, nested config object for a complete Ext page.</p>
24225  * <p>A child Component may be specified simply as a <i>config object</i>
24226  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
24227  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
24228  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
24229  * @singleton
24230  */
24231 Ext.define('Ext.ComponentManager', {
24232     extend: 'Ext.AbstractManager',
24233     alternateClassName: 'Ext.ComponentMgr',
24234     
24235     singleton: true,
24236     
24237     typeName: 'xtype',
24238     
24239     /**
24240      * Creates a new Component from the specified config object using the
24241      * config object's xtype to determine the class to instantiate.
24242      * @param {Object} config A configuration object for the Component you wish to create.
24243      * @param {Function} defaultType (optional) The constructor to provide the default Component type if
24244      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
24245      * @return {Ext.Component} The newly instantiated Component.
24246      */
24247     create: function(component, defaultType){
24248         if (component instanceof Ext.AbstractComponent) {
24249             return component;
24250         }
24251         else if (Ext.isString(component)) {
24252             return Ext.createByAlias('widget.' + component);
24253         }
24254         else {
24255             var type = component.xtype || defaultType,
24256                 config = component;
24257             
24258             return Ext.createByAlias('widget.' + type, config);
24259         }
24260     },
24261
24262     registerType: function(type, cls) {
24263         this.types[type] = cls;
24264         cls[this.typeName] = type;
24265         cls.prototype[this.typeName] = type;
24266     }
24267 });
24268 /**
24269  * An abstract base class which provides shared methods for Components across the Sencha product line.
24270  *
24271  * Please refer to sub class's documentation
24272  * @private
24273  */
24274 Ext.define('Ext.AbstractComponent', {
24275
24276     /* Begin Definitions */
24277     requires: [
24278         'Ext.ComponentQuery',
24279         'Ext.ComponentManager'
24280     ],
24281
24282     mixins: {
24283         observable: 'Ext.util.Observable',
24284         animate: 'Ext.util.Animate',
24285         state: 'Ext.state.Stateful'
24286     },
24287
24288     // The "uses" property specifies class which are used in an instantiated AbstractComponent.
24289     // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
24290     uses: [
24291         'Ext.PluginManager',
24292         'Ext.ComponentManager',
24293         'Ext.Element',
24294         'Ext.DomHelper',
24295         'Ext.XTemplate',
24296         'Ext.ComponentQuery',
24297         'Ext.ComponentLoader',
24298         'Ext.EventManager',
24299         'Ext.layout.Layout',
24300         'Ext.layout.component.Auto',
24301         'Ext.LoadMask',
24302         'Ext.ZIndexManager'
24303     ],
24304
24305     statics: {
24306         AUTO_ID: 1000
24307     },
24308
24309     /* End Definitions */
24310
24311     isComponent: true,
24312
24313     getAutoId: function() {
24314         return ++Ext.AbstractComponent.AUTO_ID;
24315     },
24316
24317
24318     /**
24319      * @cfg {String} id
24320      * The **unique id of this component instance.**
24321      *
24322      * It should not be necessary to use this configuration except for singleton objects in your application. Components
24323      * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
24324      *
24325      * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
24326      * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
24327      * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
24328      * its descendant Components by selector.
24329      *
24330      * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
24331      * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
24332      * component uniquely, and also to select sub-elements using this component's id as the parent.
24333      *
24334      * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
24335      *
24336      * **Note**: to access the container of a Component see `{@link #ownerCt}`.
24337      *
24338      * Defaults to an {@link #getId auto-assigned id}.
24339      */
24340
24341     /**
24342      * @cfg {String} itemId
24343      * An itemId can be used as an alternative way to get a reference to a component when no object reference is
24344      * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
24345      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
24346      * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
24347      * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
24348      * which requires a **unique** `{@link #id}`.
24349      *
24350      *     var c = new Ext.panel.Panel({ //
24351      *         {@link Ext.Component#height height}: 300,
24352      *         {@link #renderTo}: document.body,
24353      *         {@link Ext.container.Container#layout layout}: 'auto',
24354      *         {@link Ext.container.Container#items items}: [
24355      *             {
24356      *                 itemId: 'p1',
24357      *                 {@link Ext.panel.Panel#title title}: 'Panel 1',
24358      *                 {@link Ext.Component#height height}: 150
24359      *             },
24360      *             {
24361      *                 itemId: 'p2',
24362      *                 {@link Ext.panel.Panel#title title}: 'Panel 2',
24363      *                 {@link Ext.Component#height height}: 150
24364      *             }
24365      *         ]
24366      *     })
24367      *     p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
24368      *     p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
24369      *
24370      * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
24371      * `{@link Ext.container.Container#child}`.
24372      *
24373      * **Note**: to access the container of an item see {@link #ownerCt}.
24374      */
24375
24376     /**
24377      * @property {Ext.Container} ownerCt
24378      * This Component's owner {@link Ext.container.Container Container} (is set automatically
24379      * when this Component is added to a Container). Read-only.
24380      *
24381      * **Note**: to access items within the Container see {@link #itemId}.
24382      */
24383
24384     /**
24385      * @property {Boolean} layoutManagedWidth
24386      * @private
24387      * Flag set by the container layout to which this Component is added.
24388      * If the layout manages this Component's width, it sets the value to 1.
24389      * If it does NOT manage the width, it sets it to 2.
24390      * If the layout MAY affect the width, but only if the owning Container has a fixed width, this is set to 0.
24391      */
24392
24393     /**
24394      * @property {Boolean} layoutManagedHeight
24395      * @private
24396      * Flag set by the container layout to which this Component is added.
24397      * If the layout manages this Component's height, it sets the value to 1.
24398      * If it does NOT manage the height, it sets it to 2.
24399      * If the layout MAY affect the height, but only if the owning Container has a fixed height, this is set to 0.
24400      */
24401
24402     /**
24403      * @cfg {String/Object} autoEl
24404      * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
24405      * encapsulate this Component.
24406      *
24407      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
24408      * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
24409      * complex DOM structure specified by their own {@link #renderTpl}s.
24410      *
24411      * This is intended to allow the developer to create application-specific utility Components encapsulated by
24412      * different DOM elements. Example usage:
24413      *
24414      *     {
24415      *         xtype: 'component',
24416      *         autoEl: {
24417      *             tag: 'img',
24418      *             src: 'http://www.example.com/example.jpg'
24419      *         }
24420      *     }, {
24421      *         xtype: 'component',
24422      *         autoEl: {
24423      *             tag: 'blockquote',
24424      *             html: 'autoEl is cool!'
24425      *         }
24426      *     }, {
24427      *         xtype: 'container',
24428      *         autoEl: 'ul',
24429      *         cls: 'ux-unordered-list',
24430      *         items: {
24431      *             xtype: 'component',
24432      *             autoEl: 'li',
24433      *             html: 'First list item'
24434      *         }
24435      *     }
24436      */
24437
24438     /**
24439      * @cfg {Ext.XTemplate/String/String[]} renderTpl
24440      * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
24441      * {@link #getEl Element}.
24442      *
24443      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
24444      * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
24445      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
24446      * classes which use a more complex DOM structure, provide their own template definitions.
24447      *
24448      * This is intended to allow the developer to create application-specific utility Components with customized
24449      * internal structure.
24450      *
24451      * Upon rendering, any created child elements may be automatically imported into object properties using the
24452      * {@link #renderSelectors} and {@link #childEls} options.
24453      */
24454     renderTpl: null,
24455
24456     /**
24457      * @cfg {Object} renderData
24458      *
24459      * The data used by {@link #renderTpl} in addition to the following property values of the component:
24460      *
24461      * - id
24462      * - ui
24463      * - uiCls
24464      * - baseCls
24465      * - componentCls
24466      * - frame
24467      *
24468      * See {@link #renderSelectors} and {@link #childEls} for usage examples.
24469      */
24470
24471     /**
24472      * @cfg {Object} renderSelectors
24473      * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
24474      * created by the render process.
24475      *
24476      * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
24477      * and the found Elements are added as properties to the Component using the `renderSelector` property name.
24478      *
24479      * For example, a Component which renderes a title and description into its element:
24480      *
24481      *     Ext.create('Ext.Component', {
24482      *         renderTo: Ext.getBody(),
24483      *         renderTpl: [
24484      *             '<h1 class="title">{title}</h1>',
24485      *             '<p>{desc}</p>'
24486      *         ],
24487      *         renderData: {
24488      *             title: "Error",
24489      *             desc: "Something went wrong"
24490      *         },
24491      *         renderSelectors: {
24492      *             titleEl: 'h1.title',
24493      *             descEl: 'p'
24494      *         },
24495      *         listeners: {
24496      *             afterrender: function(cmp){
24497      *                 // After rendering the component will have a titleEl and descEl properties
24498      *                 cmp.titleEl.setStyle({color: "red"});
24499      *             }
24500      *         }
24501      *     });
24502      *
24503      * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
24504      * Component after render), see {@link #childEls} and {@link #addChildEls}.
24505      */
24506
24507     /**
24508      * @cfg {Object[]} childEls
24509      * An array describing the child elements of the Component. Each member of the array
24510      * is an object with these properties:
24511      *
24512      * - `name` - The property name on the Component for the child element.
24513      * - `itemId` - The id to combine with the Component's id that is the id of the child element.
24514      * - `id` - The id of the child element.
24515      *
24516      * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
24517      *
24518      * For example, a Component which renders a title and body text:
24519      *
24520      *     Ext.create('Ext.Component', {
24521      *         renderTo: Ext.getBody(),
24522      *         renderTpl: [
24523      *             '<h1 id="{id}-title">{title}</h1>',
24524      *             '<p>{msg}</p>',
24525      *         ],
24526      *         renderData: {
24527      *             title: "Error",
24528      *             msg: "Something went wrong"
24529      *         },
24530      *         childEls: ["title"],
24531      *         listeners: {
24532      *             afterrender: function(cmp){
24533      *                 // After rendering the component will have a title property
24534      *                 cmp.title.setStyle({color: "red"});
24535      *             }
24536      *         }
24537      *     });
24538      *
24539      * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
24540      */
24541
24542     /**
24543      * @cfg {String/HTMLElement/Ext.Element} renderTo
24544      * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
24545      *
24546      * **Notes:**
24547      *
24548      * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
24549      * It is the responsibility of the {@link Ext.container.Container Container}'s
24550      * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
24551      *
24552      * When using this config, a call to render() is not required.
24553      *
24554      * See `{@link #render}` also.
24555      */
24556
24557     /**
24558      * @cfg {Boolean} frame
24559      * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
24560      * graphical rounded frame around the Component content.
24561      *
24562      * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
24563      * Explorer prior to version 9 which do not support rounded corners natively.
24564      *
24565      * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
24566      */
24567
24568     /**
24569      * @property {Object} frameSize
24570      * Read-only property indicating the width of any framing elements which were added within the encapsulating element
24571      * to provide graphical, rounded borders. See the {@link #frame} config.
24572      *
24573      * This is an object containing the frame width in pixels for all four sides of the Component containing the
24574      * following properties:
24575      *
24576      * @property {Number} frameSize.top The width of the top framing element in pixels.
24577      * @property {Number} frameSize.right The width of the right framing element in pixels.
24578      * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
24579      * @property {Number} frameSize.left The width of the left framing element in pixels.
24580      */
24581
24582     /**
24583      * @cfg {String/Object} componentLayout
24584      * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
24585      * manager which sizes a Component's internal structure in response to the Component being sized.
24586      *
24587      * Generally, developers will not use this configuration as all provided Components which need their internal
24588      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
24589      *
24590      * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
24591      * class which simply sizes the Component's encapsulating element to the height and width specified in the
24592      * {@link #setSize} method.
24593      */
24594
24595     /**
24596      * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
24597      * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
24598      * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
24599      */
24600
24601     /**
24602      * @cfg {Object} data
24603      * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
24604      */
24605
24606     /**
24607      * @cfg {String} xtype
24608      * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
24609      * shortcut to the full componet name. For example, the component `Ext.button.Button` has an xtype of `button`.
24610      *
24611      * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
24612      * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
24613      *
24614      *     Ext.define('PressMeButton', {
24615      *         extend: 'Ext.button.Button',
24616      *         alias: 'widget.pressmebutton',
24617      *         text: 'Press Me'
24618      *     })
24619      *
24620      * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
24621      * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
24622      * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
24623      * until they are actually needed. In complex, nested layouts containing many Components, this can make a
24624      * noticeable improvement in performance.
24625      *
24626      *     // Explicit creation of contained Components:
24627      *     var panel = new Ext.Panel({
24628      *        ...
24629      *        items: [
24630      *           Ext.create('Ext.button.Button', {
24631      *              text: 'OK'
24632      *           })
24633      *        ]
24634      *     };
24635      *
24636      *     // Implicit creation using xtype:
24637      *     var panel = new Ext.Panel({
24638      *        ...
24639      *        items: [{
24640      *           xtype: 'button',
24641      *           text: 'OK'
24642      *        }]
24643      *     };
24644      *
24645      * In the first example, the button will always be created immediately during the panel's initialization. With
24646      * many added Components, this approach could potentially slow the rendering of the page. In the second example,
24647      * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
24648      * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
24649      * will never consume any resources whatsoever.
24650      */
24651
24652     /**
24653      * @cfg {String} tplWriteMode
24654      * The Ext.(X)Template method to use when updating the content area of the Component.
24655      * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
24656      */
24657     tplWriteMode: 'overwrite',
24658
24659     /**
24660      * @cfg {String} [baseCls='x-component']
24661      * The base CSS class to apply to this components's element. This will also be prepended to elements within this
24662      * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
24663      * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
24664      * componentCls to add specific styling for this component.
24665      */
24666     baseCls: Ext.baseCSSPrefix + 'component',
24667
24668     /**
24669      * @cfg {String} componentCls
24670      * CSS Class to be added to a components root level element to give distinction to it via styling.
24671      */
24672
24673     /**
24674      * @cfg {String} [cls='']
24675      * An optional extra CSS class that will be added to this component's Element. This can be useful
24676      * for adding customized styles to the component or any of its children using standard CSS rules.
24677      */
24678
24679     /**
24680      * @cfg {String} [overCls='']
24681      * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
24682      * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
24683      * component or any of its children using standard CSS rules.
24684      */
24685
24686     /**
24687      * @cfg {String} [disabledCls='x-item-disabled']
24688      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
24689      */
24690     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
24691
24692     /**
24693      * @cfg {String/String[]} ui
24694      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
24695      */
24696     ui: 'default',
24697
24698     /**
24699      * @cfg {String[]} uiCls
24700      * An array of of classNames which are currently applied to this component
24701      * @private
24702      */
24703     uiCls: [],
24704
24705     /**
24706      * @cfg {String} style
24707      * A custom style specification to be applied to this component's Element. Should be a valid argument to
24708      * {@link Ext.Element#applyStyles}.
24709      *
24710      *     new Ext.panel.Panel({
24711      *         title: 'Some Title',
24712      *         renderTo: Ext.getBody(),
24713      *         width: 400, height: 300,
24714      *         layout: 'form',
24715      *         items: [{
24716      *             xtype: 'textarea',
24717      *             style: {
24718      *                 width: '95%',
24719      *                 marginBottom: '10px'
24720      *             }
24721      *         },
24722      *         new Ext.button.Button({
24723      *             text: 'Send',
24724      *             minWidth: '100',
24725      *             style: {
24726      *                 marginBottom: '10px'
24727      *             }
24728      *         })
24729      *         ]
24730      *     });
24731      */
24732
24733     /**
24734      * @cfg {Number} width
24735      * The width of this component in pixels.
24736      */
24737
24738     /**
24739      * @cfg {Number} height
24740      * The height of this component in pixels.
24741      */
24742
24743     /**
24744      * @cfg {Number/String} border
24745      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
24746      * be a CSS style specification for each style, for example: '10 5 3 10'.
24747      */
24748
24749     /**
24750      * @cfg {Number/String} padding
24751      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
24752      * can be a CSS style specification for each style, for example: '10 5 3 10'.
24753      */
24754
24755     /**
24756      * @cfg {Number/String} margin
24757      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
24758      * be a CSS style specification for each style, for example: '10 5 3 10'.
24759      */
24760
24761     /**
24762      * @cfg {Boolean} hidden
24763      * True to hide the component.
24764      */
24765     hidden: false,
24766
24767     /**
24768      * @cfg {Boolean} disabled
24769      * True to disable the component.
24770      */
24771     disabled: false,
24772
24773     /**
24774      * @cfg {Boolean} [draggable=false]
24775      * Allows the component to be dragged.
24776      */
24777
24778     /**
24779      * @property {Boolean} draggable
24780      * Read-only property indicating whether or not the component can be dragged
24781      */
24782     draggable: false,
24783
24784     /**
24785      * @cfg {Boolean} floating
24786      * Create the Component as a floating and use absolute positioning.
24787      *
24788      * The z-index of floating Components is handled by a ZIndexManager. If you simply render a floating Component into the DOM, it will be managed
24789      * by the global {@link Ext.WindowManager WindowManager}.
24790      *
24791      * If you include a floating Component as a child item of a Container, then upon render, ExtJS will seek an ancestor floating Component to house a new
24792      * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
24793      *
24794      * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
24795      */
24796     floating: false,
24797
24798     /**
24799      * @cfg {String} hideMode
24800      * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
24801      *
24802      *   - `'display'` : The Component will be hidden using the `display: none` style.
24803      *   - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
24804      *   - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
24805      *     This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
24806      *     Component having zero dimensions.
24807      */
24808     hideMode: 'display',
24809
24810     /**
24811      * @cfg {String} contentEl
24812      * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
24813      *
24814      * This config option is used to take an existing HTML element and place it in the layout element of a new component
24815      * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
24816      *
24817      * **Notes:**
24818      *
24819      * The specified HTML element is appended to the layout element of the component _after any configured
24820      * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
24821      * the {@link #render} event is fired.
24822      *
24823      * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
24824      * scheme that the Component may use. It is just HTML. Layouts operate on child
24825      * **`{@link Ext.container.Container#items items}`**.
24826      *
24827      * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
24828      * is rendered to the panel.
24829      */
24830
24831     /**
24832      * @cfg {String/Object} [html='']
24833      * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
24834      * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
24835      * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
24836      * is appended.
24837      */
24838
24839     /**
24840      * @cfg {Boolean} styleHtmlContent
24841      * True to automatically style the html inside the content target of this component (body for panels).
24842      */
24843     styleHtmlContent: false,
24844
24845     /**
24846      * @cfg {String} [styleHtmlCls='x-html']
24847      * The class that is added to the content target when you set styleHtmlContent to true.
24848      */
24849     styleHtmlCls: Ext.baseCSSPrefix + 'html',
24850
24851     /**
24852      * @cfg {Number} minHeight
24853      * The minimum value in pixels which this Component will set its height to.
24854      *
24855      * **Warning:** This will override any size management applied by layout managers.
24856      */
24857     /**
24858      * @cfg {Number} minWidth
24859      * The minimum value in pixels which this Component will set its width to.
24860      *
24861      * **Warning:** This will override any size management applied by layout managers.
24862      */
24863     /**
24864      * @cfg {Number} maxHeight
24865      * The maximum value in pixels which this Component will set its height to.
24866      *
24867      * **Warning:** This will override any size management applied by layout managers.
24868      */
24869     /**
24870      * @cfg {Number} maxWidth
24871      * The maximum value in pixels which this Component will set its width to.
24872      *
24873      * **Warning:** This will override any size management applied by layout managers.
24874      */
24875
24876     /**
24877      * @cfg {Ext.ComponentLoader/Object} loader
24878      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
24879      */
24880
24881     /**
24882      * @cfg {Boolean} autoShow
24883      * True to automatically show the component upon creation. This config option may only be used for
24884      * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
24885      */
24886     autoShow: false,
24887
24888     /**
24889      * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
24890      * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
24891      * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
24892      * upon first _{@link #show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
24893      *
24894      * Specify as `true` to have this Component render to the document body upon first show.
24895      *
24896      * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
24897      * show.
24898      *
24899      * **This defaults to `true` for the {@link Ext.window.Window Window} class.**
24900      */
24901     autoRender: false,
24902
24903     needsLayout: false,
24904
24905     // @private
24906     allowDomMove: true,
24907
24908     /**
24909      * @cfg {Object/Object[]} plugins
24910      * An object or array of objects that will provide custom functionality for this component. The only requirement for
24911      * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
24912      * is created, if any plugins are available, the component will call the init method on each plugin, passing a
24913      * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
24914      * its functionality.
24915      */
24916
24917     /**
24918      * @property {Boolean} rendered
24919      * Read-only property indicating whether or not the component has been rendered.
24920      */
24921     rendered: false,
24922
24923     /**
24924      * @property {Number} componentLayoutCounter
24925      * @private
24926      * The number of component layout calls made on this object.
24927      */
24928     componentLayoutCounter: 0,
24929
24930     weight: 0,
24931
24932     trimRe: /^\s+|\s+$/g,
24933     spacesRe: /\s+/,
24934
24935
24936     /**
24937      * @property {Boolean} maskOnDisable
24938      * This is an internal flag that you use when creating custom components. By default this is set to true which means
24939      * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
24940      * override this property to false since they want to implement custom disable logic.
24941      */
24942     maskOnDisable: true,
24943
24944     /**
24945      * Creates new Component.
24946      * @param {Object} config  (optional) Config object.
24947      */
24948     constructor : function(config) {
24949         var me = this,
24950             i, len;
24951
24952         config = config || {};
24953         me.initialConfig = config;
24954         Ext.apply(me, config);
24955
24956         me.addEvents(
24957             /**
24958              * @event beforeactivate
24959              * Fires before a Component has been visually activated. Returning false from an event listener can prevent
24960              * the activate from occurring.
24961              * @param {Ext.Component} this
24962              */
24963             'beforeactivate',
24964             /**
24965              * @event activate
24966              * Fires after a Component has been visually activated.
24967              * @param {Ext.Component} this
24968              */
24969             'activate',
24970             /**
24971              * @event beforedeactivate
24972              * Fires before a Component has been visually deactivated. Returning false from an event listener can
24973              * prevent the deactivate from occurring.
24974              * @param {Ext.Component} this
24975              */
24976             'beforedeactivate',
24977             /**
24978              * @event deactivate
24979              * Fires after a Component has been visually deactivated.
24980              * @param {Ext.Component} this
24981              */
24982             'deactivate',
24983             /**
24984              * @event added
24985              * Fires after a Component had been added to a Container.
24986              * @param {Ext.Component} this
24987              * @param {Ext.container.Container} container Parent Container
24988              * @param {Number} pos position of Component
24989              */
24990             'added',
24991             /**
24992              * @event disable
24993              * Fires after the component is disabled.
24994              * @param {Ext.Component} this
24995              */
24996             'disable',
24997             /**
24998              * @event enable
24999              * Fires after the component is enabled.
25000              * @param {Ext.Component} this
25001              */
25002             'enable',
25003             /**
25004              * @event beforeshow
25005              * Fires before the component is shown when calling the {@link #show} method. Return false from an event
25006              * handler to stop the show.
25007              * @param {Ext.Component} this
25008              */
25009             'beforeshow',
25010             /**
25011              * @event show
25012              * Fires after the component is shown when calling the {@link #show} method.
25013              * @param {Ext.Component} this
25014              */
25015             'show',
25016             /**
25017              * @event beforehide
25018              * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
25019              * handler to stop the hide.
25020              * @param {Ext.Component} this
25021              */
25022             'beforehide',
25023             /**
25024              * @event hide
25025              * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
25026              * method.
25027              * @param {Ext.Component} this
25028              */
25029             'hide',
25030             /**
25031              * @event removed
25032              * Fires when a component is removed from an Ext.container.Container
25033              * @param {Ext.Component} this
25034              * @param {Ext.container.Container} ownerCt Container which holds the component
25035              */
25036             'removed',
25037             /**
25038              * @event beforerender
25039              * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
25040              * {@link #render}.
25041              * @param {Ext.Component} this
25042              */
25043             'beforerender',
25044             /**
25045              * @event render
25046              * Fires after the component markup is {@link #rendered}.
25047              * @param {Ext.Component} this
25048              */
25049             'render',
25050             /**
25051              * @event afterrender
25052              * Fires after the component rendering is finished.
25053              *
25054              * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
25055              * afterRender method defined for the Component.
25056              * @param {Ext.Component} this
25057              */
25058             'afterrender',
25059             /**
25060              * @event beforedestroy
25061              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the
25062              * {@link #destroy}.
25063              * @param {Ext.Component} this
25064              */
25065             'beforedestroy',
25066             /**
25067              * @event destroy
25068              * Fires after the component is {@link #destroy}ed.
25069              * @param {Ext.Component} this
25070              */
25071             'destroy',
25072             /**
25073              * @event resize
25074              * Fires after the component is resized.
25075              * @param {Ext.Component} this
25076              * @param {Number} adjWidth The box-adjusted width that was set
25077              * @param {Number} adjHeight The box-adjusted height that was set
25078              */
25079             'resize',
25080             /**
25081              * @event move
25082              * Fires after the component is moved.
25083              * @param {Ext.Component} this
25084              * @param {Number} x The new x position
25085              * @param {Number} y The new y position
25086              */
25087             'move'
25088         );
25089
25090         me.getId();
25091
25092         me.mons = [];
25093         me.additionalCls = [];
25094         me.renderData = me.renderData || {};
25095         me.renderSelectors = me.renderSelectors || {};
25096
25097         if (me.plugins) {
25098             me.plugins = [].concat(me.plugins);
25099             me.constructPlugins();
25100         }
25101
25102         me.initComponent();
25103
25104         // ititComponent gets a chance to change the id property before registering
25105         Ext.ComponentManager.register(me);
25106
25107         // Dont pass the config so that it is not applied to 'this' again
25108         me.mixins.observable.constructor.call(me);
25109         me.mixins.state.constructor.call(me, config);
25110
25111         // Save state on resize.
25112         this.addStateEvents('resize');
25113
25114         // Move this into Observable?
25115         if (me.plugins) {
25116             me.plugins = [].concat(me.plugins);
25117             for (i = 0, len = me.plugins.length; i < len; i++) {
25118                 me.plugins[i] = me.initPlugin(me.plugins[i]);
25119             }
25120         }
25121
25122         me.loader = me.getLoader();
25123
25124         if (me.renderTo) {
25125             me.render(me.renderTo);
25126             // EXTJSIV-1935 - should be a way to do afterShow or something, but that
25127             // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
25128             // implications to afterRender so we cannot do that.
25129         }
25130
25131         if (me.autoShow) {
25132             me.show();
25133         }
25134
25135         if (Ext.isDefined(me.disabledClass)) {
25136             if (Ext.isDefined(Ext.global.console)) {
25137                 Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
25138             }
25139             me.disabledCls = me.disabledClass;
25140             delete me.disabledClass;
25141         }
25142     },
25143
25144     initComponent: function () {
25145         // This is called again here to allow derived classes to add plugin configs to the
25146         // plugins array before calling down to this, the base initComponent.
25147         this.constructPlugins();
25148     },
25149
25150     /**
25151      * The supplied default state gathering method for the AbstractComponent class.
25152      *
25153      * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
25154      * state.
25155      *
25156      * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
25157      * to the result if this basic state is to be saved.
25158      *
25159      * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
25160      * configured for the document.
25161      *
25162      * @return {Object}
25163      */
25164     getState: function() {
25165         var me = this,
25166             layout = me.ownerCt ? (me.shadowOwnerCt || me.ownerCt).getLayout() : null,
25167             state = {
25168                 collapsed: me.collapsed
25169             },
25170             width = me.width,
25171             height = me.height,
25172             cm = me.collapseMemento,
25173             anchors;
25174
25175         // If a Panel-local collapse has taken place, use remembered values as the dimensions.
25176         // TODO: remove this coupling with Panel's privates! All collapse/expand logic should be refactored into one place.
25177         if (me.collapsed && cm) {
25178             if (Ext.isDefined(cm.data.width)) {
25179                 width = cm.width;
25180             }
25181             if (Ext.isDefined(cm.data.height)) {
25182                 height = cm.height;
25183             }
25184         }
25185
25186         // If we have flex, only store the perpendicular dimension.
25187         if (layout && me.flex) {
25188             state.flex = me.flex;
25189             if (layout.perpendicularPrefix) {
25190                 state[layout.perpendicularPrefix] = me['get' + layout.perpendicularPrefixCap]();
25191             } else {
25192                 if (Ext.isDefined(Ext.global.console)) {
25193                     Ext.global.console.warn('Ext.Component: Specified a flex value on a component not inside a Box layout');
25194                 }
25195             }
25196         }
25197         // If we have anchor, only store dimensions which are *not* being anchored
25198         else if (layout && me.anchor) {
25199             state.anchor = me.anchor;
25200             anchors = me.anchor.split(' ').concat(null);
25201             if (!anchors[0]) {
25202                 if (me.width) {
25203                     state.width = width;
25204                 }
25205             }
25206             if (!anchors[1]) {
25207                 if (me.height) {
25208                     state.height = height;
25209                 }
25210             }
25211         }
25212         // Store dimensions.
25213         else {
25214             if (me.width) {
25215                 state.width = width;
25216             }
25217             if (me.height) {
25218                 state.height = height;
25219             }
25220         }
25221
25222         // Don't save dimensions if they are unchanged from the original configuration.
25223         if (state.width == me.initialConfig.width) {
25224             delete state.width;
25225         }
25226         if (state.height == me.initialConfig.height) {
25227             delete state.height;
25228         }
25229
25230         // If a Box layout was managing the perpendicular dimension, don't save that dimension
25231         if (layout && layout.align && (layout.align.indexOf('stretch') !== -1)) {
25232             delete state[layout.perpendicularPrefix];
25233         }
25234         return state;
25235     },
25236
25237     show: Ext.emptyFn,
25238
25239     animate: function(animObj) {
25240         var me = this,
25241             to;
25242
25243         animObj = animObj || {};
25244         to = animObj.to || {};
25245
25246         if (Ext.fx.Manager.hasFxBlock(me.id)) {
25247             return me;
25248         }
25249         // Special processing for animating Component dimensions.
25250         if (!animObj.dynamic && (to.height || to.width)) {
25251             var curWidth = me.getWidth(),
25252                 w = curWidth,
25253                 curHeight = me.getHeight(),
25254                 h = curHeight,
25255                 needsResize = false;
25256
25257             if (to.height && to.height > curHeight) {
25258                 h = to.height;
25259                 needsResize = true;
25260             }
25261             if (to.width && to.width > curWidth) {
25262                 w = to.width;
25263                 needsResize = true;
25264             }
25265
25266             // If any dimensions are being increased, we must resize the internal structure
25267             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
25268             // The animation will then progressively reveal the larger content.
25269             if (needsResize) {
25270                 var clearWidth = !Ext.isNumber(me.width),
25271                     clearHeight = !Ext.isNumber(me.height);
25272
25273                 me.componentLayout.childrenChanged = true;
25274                 me.setSize(w, h, me.ownerCt);
25275                 me.el.setSize(curWidth, curHeight);
25276                 if (clearWidth) {
25277                     delete me.width;
25278                 }
25279                 if (clearHeight) {
25280                     delete me.height;
25281                 }
25282             }
25283         }
25284         return me.mixins.animate.animate.apply(me, arguments);
25285     },
25286
25287     /**
25288      * This method finds the topmost active layout who's processing will eventually determine the size and position of
25289      * this Component.
25290      *
25291      * This method is useful when dynamically adding Components into Containers, and some processing must take place
25292      * after the final sizing and positioning of the Component has been performed.
25293      *
25294      * @return {Ext.Component}
25295      */
25296     findLayoutController: function() {
25297         return this.findParentBy(function(c) {
25298             // Return true if we are at the root of the Container tree
25299             // or this Container's layout is busy but the next one up is not.
25300             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
25301         });
25302     },
25303
25304     onShow : function() {
25305         // Layout if needed
25306         var needsLayout = this.needsLayout;
25307         if (Ext.isObject(needsLayout)) {
25308             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
25309         }
25310     },
25311
25312     constructPlugin: function(plugin) {
25313         if (plugin.ptype && typeof plugin.init != 'function') {
25314             plugin.cmp = this;
25315             plugin = Ext.PluginManager.create(plugin);
25316         }
25317         else if (typeof plugin == 'string') {
25318             plugin = Ext.PluginManager.create({
25319                 ptype: plugin,
25320                 cmp: this
25321             });
25322         }
25323         return plugin;
25324     },
25325
25326     /**
25327      * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
25328      * appropriate instances.
25329      */
25330     constructPlugins: function() {
25331         var me = this,
25332             plugins = me.plugins,
25333             i, len;
25334
25335         if (plugins) {
25336             for (i = 0, len = plugins.length; i < len; i++) {
25337                 // this just returns already-constructed plugin instances...
25338                 plugins[i] = me.constructPlugin(plugins[i]);
25339             }
25340         }
25341     },
25342
25343     // @private
25344     initPlugin : function(plugin) {
25345         plugin.init(this);
25346
25347         return plugin;
25348     },
25349
25350     /**
25351      * Handles autoRender. Floating Components may have an ownerCt. If they are asking to be constrained, constrain them
25352      * within that ownerCt, and have their z-index managed locally. Floating Components are always rendered to
25353      * document.body
25354      */
25355     doAutoRender: function() {
25356         var me = this;
25357         if (me.floating) {
25358             me.render(document.body);
25359         } else {
25360             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
25361         }
25362     },
25363
25364     // @private
25365     render : function(container, position) {
25366         var me = this;
25367
25368         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
25369
25370             // Flag set during the render process.
25371             // It can be used to inhibit event-driven layout calls during the render phase
25372             me.rendering = true;
25373
25374             // If this.el is defined, we want to make sure we are dealing with
25375             // an Ext Element.
25376             if (me.el) {
25377                 me.el = Ext.get(me.el);
25378             }
25379
25380             // Perform render-time processing for floating Components
25381             if (me.floating) {
25382                 me.onFloatRender();
25383             }
25384
25385             container = me.initContainer(container);
25386
25387             me.onRender(container, position);
25388
25389             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
25390             // This means DISPLAY, VISIBILITY or OFFSETS.
25391             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
25392
25393             if (me.overCls) {
25394                 me.el.hover(me.addOverCls, me.removeOverCls, me);
25395             }
25396
25397             me.fireEvent('render', me);
25398
25399             me.initContent();
25400
25401             me.afterRender(container);
25402             me.fireEvent('afterrender', me);
25403
25404             me.initEvents();
25405
25406             if (me.hidden) {
25407                 // Hiding during the render process should not perform any ancillary
25408                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
25409                 // So just make the element hidden according to the configured hideMode
25410                 me.el.hide();
25411             }
25412
25413             if (me.disabled) {
25414                 // pass silent so the event doesn't fire the first time.
25415                 me.disable(true);
25416             }
25417
25418             // Delete the flag once the rendering is done.
25419             delete me.rendering;
25420         }
25421         return me;
25422     },
25423
25424     // @private
25425     onRender : function(container, position) {
25426         var me = this,
25427             el = me.el,
25428             styles = me.initStyles(),
25429             renderTpl, renderData, i;
25430
25431         position = me.getInsertPosition(position);
25432
25433         if (!el) {
25434             if (position) {
25435                 el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
25436             }
25437             else {
25438                 el = Ext.DomHelper.append(container, me.getElConfig(), true);
25439             }
25440         }
25441         else if (me.allowDomMove !== false) {
25442             if (position) {
25443                 container.dom.insertBefore(el.dom, position);
25444             } else {
25445                 container.dom.appendChild(el.dom);
25446             }
25447         }
25448
25449         if (Ext.scopeResetCSS && !me.ownerCt) {
25450             // If this component's el is the body element, we add the reset class to the html tag
25451             if (el.dom == Ext.getBody().dom) {
25452                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
25453             }
25454             else {
25455                 // Else we wrap this element in an element that adds the reset class.
25456                 me.resetEl = el.wrap({
25457                     cls: Ext.baseCSSPrefix + 'reset'
25458                 });
25459             }
25460         }
25461
25462         me.setUI(me.ui);
25463
25464         el.addCls(me.initCls());
25465         el.setStyle(styles);
25466
25467         // Here we check if the component has a height set through style or css.
25468         // If it does then we set the this.height to that value and it won't be
25469         // considered an auto height component
25470         // if (this.height === undefined) {
25471         //     var height = el.getHeight();
25472         //     // This hopefully means that the panel has an explicit height set in style or css
25473         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
25474         //         this.height = height;
25475         //     }
25476         // }
25477
25478         me.el = el;
25479
25480         me.initFrame();
25481
25482         renderTpl = me.initRenderTpl();
25483         if (renderTpl) {
25484             renderData = me.initRenderData();
25485             renderTpl.append(me.getTargetEl(), renderData);
25486         }
25487
25488         me.applyRenderSelectors();
25489
25490         me.rendered = true;
25491     },
25492
25493     // @private
25494     afterRender : function() {
25495         var me = this,
25496             pos,
25497             xy;
25498
25499         me.getComponentLayout();
25500
25501         // Set the size if a size is configured, or if this is the outermost Container.
25502         // Also, if this is a collapsed Panel, it needs an initial component layout
25503         // to lay out its header so that it can have a height determined.
25504         if (me.collapsed || (!me.ownerCt || (me.height || me.width))) {
25505             me.setSize(me.width, me.height);
25506         } else {
25507             // It is expected that child items be rendered before this method returns and
25508             // the afterrender event fires. Since we aren't going to do the layout now, we
25509             // must render the child items. This is handled implicitly above in the layout
25510             // caused by setSize.
25511             me.renderChildren();
25512         }
25513
25514         // For floaters, calculate x and y if they aren't defined by aligning
25515         // the sized element to the center of either the container or the ownerCt
25516         if (me.floating && (me.x === undefined || me.y === undefined)) {
25517             if (me.floatParent) {
25518                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
25519                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
25520             } else {
25521                 xy = me.el.getAlignToXY(me.container, 'c-c');
25522                 pos = me.container.translatePoints(xy[0], xy[1]);
25523             }
25524             me.x = me.x === undefined ? pos.left: me.x;
25525             me.y = me.y === undefined ? pos.top: me.y;
25526         }
25527
25528         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
25529             me.setPosition(me.x, me.y);
25530         }
25531
25532         if (me.styleHtmlContent) {
25533             me.getTargetEl().addCls(me.styleHtmlCls);
25534         }
25535     },
25536
25537     /**
25538      * @private
25539      * Called by Component#doAutoRender
25540      *
25541      * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
25542      *
25543      * Components added in ths way will not participate in any layout, but will be rendered
25544      * upon first show in the way that {@link Ext.window.Window Window}s are.
25545      */
25546     registerFloatingItem: function(cmp) {
25547         var me = this;
25548         if (!me.floatingItems) {
25549             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
25550         }
25551         me.floatingItems.register(cmp);
25552     },
25553
25554     renderChildren: function () {
25555         var me = this,
25556             layout = me.getComponentLayout();
25557
25558         me.suspendLayout = true;
25559         layout.renderChildren();
25560         delete me.suspendLayout;
25561     },
25562
25563     frameCls: Ext.baseCSSPrefix + 'frame',
25564
25565     frameIdRegex: /[-]frame\d+[TMB][LCR]$/,
25566
25567     frameElementCls: {
25568         tl: [],
25569         tc: [],
25570         tr: [],
25571         ml: [],
25572         mc: [],
25573         mr: [],
25574         bl: [],
25575         bc: [],
25576         br: []
25577     },
25578
25579     frameTpl: [
25580         '<tpl if="top">',
25581             '<tpl if="left"><div id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
25582                 '<tpl if="right"><div id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
25583                     '<div id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></div>',
25584                 '<tpl if="right"></div></tpl>',
25585             '<tpl if="left"></div></tpl>',
25586         '</tpl>',
25587         '<tpl if="left"><div id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></tpl>',
25588             '<tpl if="right"><div id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
25589                 '<div id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" role="presentation"></div>',
25590             '<tpl if="right"></div></tpl>',
25591         '<tpl if="left"></div></tpl>',
25592         '<tpl if="bottom">',
25593             '<tpl if="left"><div id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
25594                 '<tpl if="right"><div id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"></tpl>',
25595                     '<div id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></div>',
25596                 '<tpl if="right"></div></tpl>',
25597             '<tpl if="left"></div></tpl>',
25598         '</tpl>'
25599     ],
25600
25601     frameTableTpl: [
25602         '<table><tbody>',
25603             '<tpl if="top">',
25604                 '<tr>',
25605                     '<tpl if="left"><td id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"></td></tpl>',
25606                     '<td id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></td>',
25607                     '<tpl if="right"><td id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25608                 '</tr>',
25609             '</tpl>',
25610             '<tr>',
25611                 '<tpl if="left"><td id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25612                 '<td id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" style="background-position: 0 0;" role="presentation"></td>',
25613                 '<tpl if="right"><td id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25614             '</tr>',
25615             '<tpl if="bottom">',
25616                 '<tr>',
25617                     '<tpl if="left"><td id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25618                     '<td id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></td>',
25619                     '<tpl if="right"><td id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
25620                 '</tr>',
25621             '</tpl>',
25622         '</tbody></table>'
25623     ],
25624
25625     /**
25626      * @private
25627      */
25628     initFrame : function() {
25629         if (Ext.supports.CSS3BorderRadius) {
25630             return false;
25631         }
25632
25633         var me = this,
25634             frameInfo = me.getFrameInfo(),
25635             frameWidth = frameInfo.width,
25636             frameTpl = me.getFrameTpl(frameInfo.table),
25637             frameGenId;
25638
25639         if (me.frame) {
25640             // since we render id's into the markup and id's NEED to be unique, we have a
25641             // simple strategy for numbering their generations.
25642             me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
25643             frameGenId = me.id + '-frame' + frameGenId;
25644
25645             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
25646             frameTpl.insertFirst(me.el, Ext.apply({}, {
25647                 fgid:       frameGenId,
25648                 ui:         me.ui,
25649                 uiCls:      me.uiCls,
25650                 frameCls:   me.frameCls,
25651                 baseCls:    me.baseCls,
25652                 frameWidth: frameWidth,
25653                 top:        !!frameInfo.top,
25654                 left:       !!frameInfo.left,
25655                 right:      !!frameInfo.right,
25656                 bottom:     !!frameInfo.bottom
25657             }, me.getFramePositions(frameInfo)));
25658
25659             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
25660             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
25661
25662             // Clean out the childEls for the old frame elements (the majority of the els)
25663             me.removeChildEls(function (c) {
25664                 return c.id && me.frameIdRegex.test(c.id);
25665             });
25666
25667             // Add the childEls for each of the new frame elements
25668             Ext.each(['TL','TC','TR','ML','MC','MR','BL','BC','BR'], function (suffix) {
25669                 me.childEls.push({ name: 'frame' + suffix, id: frameGenId + suffix });
25670             });
25671         }
25672     },
25673
25674     updateFrame: function() {
25675         if (Ext.supports.CSS3BorderRadius) {
25676             return false;
25677         }
25678
25679         var me = this,
25680             wasTable = this.frameSize && this.frameSize.table,
25681             oldFrameTL = this.frameTL,
25682             oldFrameBL = this.frameBL,
25683             oldFrameML = this.frameML,
25684             oldFrameMC = this.frameMC,
25685             newMCClassName;
25686
25687         this.initFrame();
25688
25689         if (oldFrameMC) {
25690             if (me.frame) {
25691                 // Reapply render selectors
25692                 delete me.frameTL;
25693                 delete me.frameTC;
25694                 delete me.frameTR;
25695                 delete me.frameML;
25696                 delete me.frameMC;
25697                 delete me.frameMR;
25698                 delete me.frameBL;
25699                 delete me.frameBC;
25700                 delete me.frameBR;
25701                 this.applyRenderSelectors();
25702
25703                 // Store the class names set on the new mc
25704                 newMCClassName = this.frameMC.dom.className;
25705
25706                 // Replace the new mc with the old mc
25707                 oldFrameMC.insertAfter(this.frameMC);
25708                 this.frameMC.remove();
25709
25710                 // Restore the reference to the old frame mc as the framebody
25711                 this.frameBody = this.frameMC = oldFrameMC;
25712
25713                 // Apply the new mc classes to the old mc element
25714                 oldFrameMC.dom.className = newMCClassName;
25715
25716                 // Remove the old framing
25717                 if (wasTable) {
25718                     me.el.query('> table')[1].remove();
25719                 }
25720                 else {
25721                     if (oldFrameTL) {
25722                         oldFrameTL.remove();
25723                     }
25724                     if (oldFrameBL) {
25725                         oldFrameBL.remove();
25726                     }
25727                     oldFrameML.remove();
25728                 }
25729             }
25730             else {
25731                 // We were framed but not anymore. Move all content from the old frame to the body
25732
25733             }
25734         }
25735         else if (me.frame) {
25736             this.applyRenderSelectors();
25737         }
25738     },
25739
25740     getFrameInfo: function() {
25741         if (Ext.supports.CSS3BorderRadius) {
25742             return false;
25743         }
25744
25745         var me = this,
25746             left = me.el.getStyle('background-position-x'),
25747             top = me.el.getStyle('background-position-y'),
25748             info, frameInfo = false, max;
25749
25750         // Some browsers dont support background-position-x and y, so for those
25751         // browsers let's split background-position into two parts.
25752         if (!left && !top) {
25753             info = me.el.getStyle('background-position').split(' ');
25754             left = info[0];
25755             top = info[1];
25756         }
25757
25758         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
25759         // the background position of this.el from the css to indicate to IE that this component needs
25760         // framing. We parse it here and change the markup accordingly.
25761         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
25762             max = Math.max;
25763
25764             frameInfo = {
25765                 // Table markup starts with 110, div markup with 100.
25766                 table: left.substr(0, 3) == '110',
25767
25768                 // Determine if we are dealing with a horizontal or vertical component
25769                 vertical: top.substr(0, 3) == '110',
25770
25771                 // Get and parse the different border radius sizes
25772                 top:    max(left.substr(3, 2), left.substr(5, 2)),
25773                 right:  max(left.substr(5, 2), top.substr(3, 2)),
25774                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
25775                 left:   max(top.substr(5, 2), left.substr(3, 2))
25776             };
25777
25778             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
25779
25780             // Just to be sure we set the background image of the el to none.
25781             me.el.setStyle('background-image', 'none');
25782         }
25783
25784         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
25785         // This way IE can't figure out what sizes to use and thus framing can't work.
25786         if (me.frame === true && !frameInfo) {
25787             Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
25788                             "framing defined in the CSS template. In this case IE can't figure out what sizes " +
25789                             "to use and thus framing on this component will be disabled.");
25790         }
25791
25792         me.frame = me.frame || !!frameInfo;
25793         me.frameSize = frameInfo || false;
25794
25795         return frameInfo;
25796     },
25797
25798     getFramePositions: function(frameInfo) {
25799         var me = this,
25800             frameWidth = frameInfo.width,
25801             dock = me.dock,
25802             positions, tc, bc, ml, mr;
25803
25804         if (frameInfo.vertical) {
25805             tc = '0 -' + (frameWidth * 0) + 'px';
25806             bc = '0 -' + (frameWidth * 1) + 'px';
25807
25808             if (dock && dock == "right") {
25809                 tc = 'right -' + (frameWidth * 0) + 'px';
25810                 bc = 'right -' + (frameWidth * 1) + 'px';
25811             }
25812
25813             positions = {
25814                 tl: '0 -' + (frameWidth * 0) + 'px',
25815                 tr: '0 -' + (frameWidth * 1) + 'px',
25816                 bl: '0 -' + (frameWidth * 2) + 'px',
25817                 br: '0 -' + (frameWidth * 3) + 'px',
25818
25819                 ml: '-' + (frameWidth * 1) + 'px 0',
25820                 mr: 'right 0',
25821
25822                 tc: tc,
25823                 bc: bc
25824             };
25825         } else {
25826             ml = '-' + (frameWidth * 0) + 'px 0';
25827             mr = 'right 0';
25828
25829             if (dock && dock == "bottom") {
25830                 ml = 'left bottom';
25831                 mr = 'right bottom';
25832             }
25833
25834             positions = {
25835                 tl: '0 -' + (frameWidth * 2) + 'px',
25836                 tr: 'right -' + (frameWidth * 3) + 'px',
25837                 bl: '0 -' + (frameWidth * 4) + 'px',
25838                 br: 'right -' + (frameWidth * 5) + 'px',
25839
25840                 ml: ml,
25841                 mr: mr,
25842
25843                 tc: '0 -' + (frameWidth * 0) + 'px',
25844                 bc: '0 -' + (frameWidth * 1) + 'px'
25845             };
25846         }
25847
25848         return positions;
25849     },
25850
25851     /**
25852      * @private
25853      */
25854     getFrameTpl : function(table) {
25855         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
25856     },
25857
25858     /**
25859      * Creates an array of class names from the configurations to add to this Component's `el` on render.
25860      *
25861      * Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.
25862      *
25863      * @return {String[]} An array of class names with which the Component's element will be rendered.
25864      * @private
25865      */
25866     initCls: function() {
25867         var me = this,
25868             cls = [];
25869
25870         cls.push(me.baseCls);
25871
25872         if (Ext.isDefined(me.cmpCls)) {
25873             if (Ext.isDefined(Ext.global.console)) {
25874                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
25875             }
25876             me.componentCls = me.cmpCls;
25877             delete me.cmpCls;
25878         }
25879
25880         if (me.componentCls) {
25881             cls.push(me.componentCls);
25882         } else {
25883             me.componentCls = me.baseCls;
25884         }
25885         if (me.cls) {
25886             cls.push(me.cls);
25887             delete me.cls;
25888         }
25889
25890         return cls.concat(me.additionalCls);
25891     },
25892
25893     /**
25894      * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
25895      * uiCls set on the component and rename them so they include the new UI
25896      * @param {String} ui The new UI for the component
25897      */
25898     setUI: function(ui) {
25899         var me = this,
25900             oldUICls = Ext.Array.clone(me.uiCls),
25901             newUICls = [],
25902             classes = [],
25903             cls,
25904             i;
25905
25906         //loop through all exisiting uiCls and update the ui in them
25907         for (i = 0; i < oldUICls.length; i++) {
25908             cls = oldUICls[i];
25909
25910             classes = classes.concat(me.removeClsWithUI(cls, true));
25911             newUICls.push(cls);
25912         }
25913
25914         if (classes.length) {
25915             me.removeCls(classes);
25916         }
25917
25918         //remove the UI from the element
25919         me.removeUIFromElement();
25920
25921         //set the UI
25922         me.ui = ui;
25923
25924         //add the new UI to the elemend
25925         me.addUIToElement();
25926
25927         //loop through all exisiting uiCls and update the ui in them
25928         classes = [];
25929         for (i = 0; i < newUICls.length; i++) {
25930             cls = newUICls[i];
25931             classes = classes.concat(me.addClsWithUI(cls, true));
25932         }
25933
25934         if (classes.length) {
25935             me.addCls(classes);
25936         }
25937     },
25938
25939     /**
25940      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
25941      * component.
25942      * @param {String/String[]} cls A string or an array of strings to add to the uiCls
25943      * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
25944      */
25945     addClsWithUI: function(cls, skip) {
25946         var me = this,
25947             classes = [],
25948             i;
25949
25950         if (!Ext.isArray(cls)) {
25951             cls = [cls];
25952         }
25953
25954         for (i = 0; i < cls.length; i++) {
25955             if (cls[i] && !me.hasUICls(cls[i])) {
25956                 me.uiCls = Ext.Array.clone(me.uiCls);
25957                 me.uiCls.push(cls[i]);
25958
25959                 classes = classes.concat(me.addUIClsToElement(cls[i]));
25960             }
25961         }
25962
25963         if (skip !== true) {
25964             me.addCls(classes);
25965         }
25966
25967         return classes;
25968     },
25969
25970     /**
25971      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
25972      * elements of this component.
25973      * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
25974      */
25975     removeClsWithUI: function(cls, skip) {
25976         var me = this,
25977             classes = [],
25978             i;
25979
25980         if (!Ext.isArray(cls)) {
25981             cls = [cls];
25982         }
25983
25984         for (i = 0; i < cls.length; i++) {
25985             if (cls[i] && me.hasUICls(cls[i])) {
25986                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
25987
25988                 classes = classes.concat(me.removeUIClsFromElement(cls[i]));
25989             }
25990         }
25991
25992         if (skip !== true) {
25993             me.removeCls(classes);
25994         }
25995
25996         return classes;
25997     },
25998
25999     /**
26000      * Checks if there is currently a specified uiCls
26001      * @param {String} cls The cls to check
26002      */
26003     hasUICls: function(cls) {
26004         var me = this,
26005             uiCls = me.uiCls || [];
26006
26007         return Ext.Array.contains(uiCls, cls);
26008     },
26009
26010     /**
26011      * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
26012      * than just the components element.
26013      * @param {String} ui The UI to remove from the element
26014      */
26015     addUIClsToElement: function(cls, force) {
26016         var me = this,
26017             result = [],
26018             frameElementCls = me.frameElementCls;
26019
26020         result.push(Ext.baseCSSPrefix + cls);
26021         result.push(me.baseCls + '-' + cls);
26022         result.push(me.baseCls + '-' + me.ui + '-' + cls);
26023
26024         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
26025             // define each element of the frame
26026             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26027                 classes, i, j, el;
26028
26029             // loop through each of them, and if they are defined add the ui
26030             for (i = 0; i < els.length; i++) {
26031                 el = me['frame' + els[i].toUpperCase()];
26032                 classes = [me.baseCls + '-' + me.ui + '-' + els[i], me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]];
26033                 if (el && el.dom) {
26034                     el.addCls(classes);
26035                 } else {
26036                     for (j = 0; j < classes.length; j++) {
26037                         if (Ext.Array.indexOf(frameElementCls[els[i]], classes[j]) == -1) {
26038                             frameElementCls[els[i]].push(classes[j]);
26039                         }
26040                     }
26041                 }
26042             }
26043         }
26044
26045         me.frameElementCls = frameElementCls;
26046
26047         return result;
26048     },
26049
26050     /**
26051      * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
26052      * will be: `this.baseCls + '-' + ui`
26053      * @param {String} ui The UI to add to the element
26054      */
26055     removeUIClsFromElement: function(cls, force) {
26056         var me = this,
26057             result = [],
26058             frameElementCls = me.frameElementCls;
26059
26060         result.push(Ext.baseCSSPrefix + cls);
26061         result.push(me.baseCls + '-' + cls);
26062         result.push(me.baseCls + '-' + me.ui + '-' + cls);
26063
26064         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
26065             // define each element of the frame
26066             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26067                 i, el;
26068             cls = me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i];
26069             // loop through each of them, and if they are defined add the ui
26070             for (i = 0; i < els.length; i++) {
26071                 el = me['frame' + els[i].toUpperCase()];
26072                 if (el && el.dom) {
26073                     el.removeCls(cls);
26074                 } else {
26075                     Ext.Array.remove(frameElementCls[els[i]], cls);
26076                 }
26077             }
26078         }
26079
26080         me.frameElementCls = frameElementCls;
26081
26082         return result;
26083     },
26084
26085     /**
26086      * Method which adds a specified UI to the components element.
26087      * @private
26088      */
26089     addUIToElement: function(force) {
26090         var me = this,
26091             frameElementCls = me.frameElementCls;
26092
26093         me.addCls(me.baseCls + '-' + me.ui);
26094
26095         if (me.frame && !Ext.supports.CSS3BorderRadius) {
26096             // define each element of the frame
26097             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26098                 i, el, cls;
26099
26100             // loop through each of them, and if they are defined add the ui
26101             for (i = 0; i < els.length; i++) {
26102                 el = me['frame' + els[i].toUpperCase()];
26103                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
26104                 if (el) {
26105                     el.addCls(cls);
26106                 } else {
26107                     if (!Ext.Array.contains(frameElementCls[els[i]], cls)) {
26108                         frameElementCls[els[i]].push(cls);
26109                     }
26110                 }
26111             }
26112         }
26113     },
26114
26115     /**
26116      * Method which removes a specified UI from the components element.
26117      * @private
26118      */
26119     removeUIFromElement: function() {
26120         var me = this,
26121             frameElementCls = me.frameElementCls;
26122
26123         me.removeCls(me.baseCls + '-' + me.ui);
26124
26125         if (me.frame && !Ext.supports.CSS3BorderRadius) {
26126             // define each element of the frame
26127             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
26128                 i, j, el, cls;
26129
26130             // loop through each of them, and if they are defined add the ui
26131             for (i = 0; i < els.length; i++) {
26132                 el = me['frame' + els[i].toUpperCase()];
26133                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
26134
26135                 if (el) {
26136                     el.removeCls(cls);
26137                 } else {
26138                     Ext.Array.remove(frameElementCls[els[i]], cls);
26139                 }
26140             }
26141         }
26142     },
26143
26144     getElConfig : function() {
26145         if (Ext.isString(this.autoEl)) {
26146             this.autoEl = {
26147                 tag: this.autoEl
26148             };
26149         }
26150
26151         var result = this.autoEl || {tag: 'div'};
26152         result.id = this.id;
26153         return result;
26154     },
26155
26156     /**
26157      * This function takes the position argument passed to onRender and returns a DOM element that you can use in the
26158      * insertBefore.
26159      * @param {String/Number/Ext.Element/HTMLElement} position Index, element id or element you want to put this
26160      * component before.
26161      * @return {HTMLElement} DOM element that you can use in the insertBefore
26162      */
26163     getInsertPosition: function(position) {
26164         // Convert the position to an element to insert before
26165         if (position !== undefined) {
26166             if (Ext.isNumber(position)) {
26167                 position = this.container.dom.childNodes[position];
26168             }
26169             else {
26170                 position = Ext.getDom(position);
26171             }
26172         }
26173
26174         return position;
26175     },
26176
26177     /**
26178      * Adds ctCls to container.
26179      * @return {Ext.Element} The initialized container
26180      * @private
26181      */
26182     initContainer: function(container) {
26183         var me = this;
26184
26185         // If you render a component specifying the el, we get the container
26186         // of the el, and make sure we dont move the el around in the dom
26187         // during the render
26188         if (!container && me.el) {
26189             container = me.el.dom.parentNode;
26190             me.allowDomMove = false;
26191         }
26192
26193         me.container = Ext.get(container);
26194
26195         if (me.ctCls) {
26196             me.container.addCls(me.ctCls);
26197         }
26198
26199         return me.container;
26200     },
26201
26202     /**
26203      * Initialized the renderData to be used when rendering the renderTpl.
26204      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
26205      * @private
26206      */
26207     initRenderData: function() {
26208         var me = this;
26209
26210         return Ext.applyIf(me.renderData, {
26211             id: me.id,
26212             ui: me.ui,
26213             uiCls: me.uiCls,
26214             baseCls: me.baseCls,
26215             componentCls: me.componentCls,
26216             frame: me.frame
26217         });
26218     },
26219
26220     /**
26221      * @private
26222      */
26223     getTpl: function(name) {
26224         var me = this,
26225             prototype = me.self.prototype,
26226             ownerPrototype,
26227             tpl;
26228
26229         if (me.hasOwnProperty(name)) {
26230             tpl = me[name];
26231             if (tpl && !(tpl instanceof Ext.XTemplate)) {
26232                 me[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
26233             }
26234
26235             return me[name];
26236         }
26237
26238         if (!(prototype[name] instanceof Ext.XTemplate)) {
26239             ownerPrototype = prototype;
26240
26241             do {
26242                 if (ownerPrototype.hasOwnProperty(name)) {
26243                     tpl = ownerPrototype[name];
26244                     if (tpl && !(tpl instanceof Ext.XTemplate)) {
26245                         ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
26246                         break;
26247                     }
26248                 }
26249
26250                 ownerPrototype = ownerPrototype.superclass;
26251             } while (ownerPrototype);
26252         }
26253
26254         return prototype[name];
26255     },
26256
26257     /**
26258      * Initializes the renderTpl.
26259      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
26260      * @private
26261      */
26262     initRenderTpl: function() {
26263         return this.getTpl('renderTpl');
26264     },
26265
26266     /**
26267      * Converts style definitions to String.
26268      * @return {String} A CSS style string with style, padding, margin and border.
26269      * @private
26270      */
26271     initStyles: function() {
26272         var style = {},
26273             me = this,
26274             Element = Ext.Element;
26275
26276         if (Ext.isString(me.style)) {
26277             style = Element.parseStyles(me.style);
26278         } else {
26279             style = Ext.apply({}, me.style);
26280         }
26281
26282         // Convert the padding, margin and border properties from a space seperated string
26283         // into a proper style string
26284         if (me.padding !== undefined) {
26285             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
26286         }
26287
26288         if (me.margin !== undefined) {
26289             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
26290         }
26291
26292         delete me.style;
26293         return style;
26294     },
26295
26296     /**
26297      * Initializes this components contents. It checks for the properties html, contentEl and tpl/data.
26298      * @private
26299      */
26300     initContent: function() {
26301         var me = this,
26302             target = me.getTargetEl(),
26303             contentEl,
26304             pre;
26305
26306         if (me.html) {
26307             target.update(Ext.DomHelper.markup(me.html));
26308             delete me.html;
26309         }
26310
26311         if (me.contentEl) {
26312             contentEl = Ext.get(me.contentEl);
26313             pre = Ext.baseCSSPrefix;
26314             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
26315             target.appendChild(contentEl.dom);
26316         }
26317
26318         if (me.tpl) {
26319             // Make sure this.tpl is an instantiated XTemplate
26320             if (!me.tpl.isTemplate) {
26321                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
26322             }
26323
26324             if (me.data) {
26325                 me.tpl[me.tplWriteMode](target, me.data);
26326                 delete me.data;
26327             }
26328         }
26329     },
26330
26331     // @private
26332     initEvents : function() {
26333         var me = this,
26334             afterRenderEvents = me.afterRenderEvents,
26335             el,
26336             property,
26337             fn = function(listeners){
26338                 me.mon(el, listeners);
26339             };
26340         if (afterRenderEvents) {
26341             for (property in afterRenderEvents) {
26342                 if (afterRenderEvents.hasOwnProperty(property)) {
26343                     el = me[property];
26344                     if (el && el.on) {
26345                         Ext.each(afterRenderEvents[property], fn);
26346                     }
26347                 }
26348             }
26349         }
26350     },
26351
26352     /**
26353      * Adds each argument passed to this method to the {@link #childEls} array.
26354      */
26355     addChildEls: function () {
26356         var me = this,
26357             childEls = me.childEls || (me.childEls = []);
26358
26359         childEls.push.apply(childEls, arguments);
26360     },
26361
26362     /**
26363      * Removes items in the childEls array based on the return value of a supplied test function. The function is called
26364      * with a entry in childEls and if the test function return true, that entry is removed. If false, that entry is
26365      * kept.
26366      * @param {Function} testFn The test function.
26367      */
26368     removeChildEls: function (testFn) {
26369         var me = this,
26370             old = me.childEls,
26371             keepers = (me.childEls = []),
26372             n, i, cel;
26373
26374         for (i = 0, n = old.length; i < n; ++i) {
26375             cel = old[i];
26376             if (!testFn(cel)) {
26377                 keepers.push(cel);
26378             }
26379         }
26380     },
26381
26382     /**
26383      * Sets references to elements inside the component. This applies {@link #renderSelectors}
26384      * as well as {@link #childEls}.
26385      * @private
26386      */
26387     applyRenderSelectors: function() {
26388         var me = this,
26389             childEls = me.childEls,
26390             selectors = me.renderSelectors,
26391             el = me.el,
26392             dom = el.dom,
26393             baseId, childName, childId, i, selector;
26394
26395         if (childEls) {
26396             baseId = me.id + '-';
26397             for (i = childEls.length; i--; ) {
26398                 childName = childId = childEls[i];
26399                 if (typeof(childName) != 'string') {
26400                     childId = childName.id || (baseId + childName.itemId);
26401                     childName = childName.name;
26402                 } else {
26403                     childId = baseId + childId;
26404                 }
26405
26406                 // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
26407                 // we know the el's are children of our el we use getById instead:
26408                 me[childName] = el.getById(childId);
26409             }
26410         }
26411
26412         // We still support renderSelectors. There are a few places in the framework that
26413         // need them and they are a documented part of the API. In fact, we support mixing
26414         // childEls and renderSelectors (no reason not to).
26415         if (selectors) {
26416             for (selector in selectors) {
26417                 if (selectors.hasOwnProperty(selector) && selectors[selector]) {
26418                     me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
26419                 }
26420             }
26421         }
26422     },
26423
26424     /**
26425      * Tests whether this Component matches the selector string.
26426      * @param {String} selector The selector string to test against.
26427      * @return {Boolean} True if this Component matches the selector.
26428      */
26429     is: function(selector) {
26430         return Ext.ComponentQuery.is(this, selector);
26431     },
26432
26433     /**
26434      * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
26435      *
26436      * Example:
26437      *
26438      *     var owningTabPanel = grid.up('tabpanel');
26439      *
26440      * @param {String} [selector] The simple selector to test.
26441      * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
26442      */
26443     up: function(selector) {
26444         var result = this.ownerCt;
26445         if (selector) {
26446             for (; result; result = result.ownerCt) {
26447                 if (Ext.ComponentQuery.is(result, selector)) {
26448                     return result;
26449                 }
26450             }
26451         }
26452         return result;
26453     },
26454
26455     /**
26456      * Returns the next sibling of this Component.
26457      *
26458      * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
26459      *
26460      * May also be refered to as **`next()`**
26461      *
26462      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
26463      * {@link #nextNode}
26464      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
26465      * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
26466      * Returns null if there is no matching sibling.
26467      */
26468     nextSibling: function(selector) {
26469         var o = this.ownerCt, it, last, idx, c;
26470         if (o) {
26471             it = o.items;
26472             idx = it.indexOf(this) + 1;
26473             if (idx) {
26474                 if (selector) {
26475                     for (last = it.getCount(); idx < last; idx++) {
26476                         if ((c = it.getAt(idx)).is(selector)) {
26477                             return c;
26478                         }
26479                     }
26480                 } else {
26481                     if (idx < it.getCount()) {
26482                         return it.getAt(idx);
26483                     }
26484                 }
26485             }
26486         }
26487         return null;
26488     },
26489
26490     /**
26491      * Returns the previous sibling of this Component.
26492      *
26493      * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
26494      * selector.
26495      *
26496      * May also be refered to as **`prev()`**
26497      *
26498      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
26499      * {@link #previousNode}
26500      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
26501      * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
26502      * Returns null if there is no matching sibling.
26503      */
26504     previousSibling: function(selector) {
26505         var o = this.ownerCt, it, idx, c;
26506         if (o) {
26507             it = o.items;
26508             idx = it.indexOf(this);
26509             if (idx != -1) {
26510                 if (selector) {
26511                     for (--idx; idx >= 0; idx--) {
26512                         if ((c = it.getAt(idx)).is(selector)) {
26513                             return c;
26514                         }
26515                     }
26516                 } else {
26517                     if (idx) {
26518                         return it.getAt(--idx);
26519                     }
26520                 }
26521             }
26522         }
26523         return null;
26524     },
26525
26526     /**
26527      * Returns the previous node in the Component tree in tree traversal order.
26528      *
26529      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
26530      * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
26531      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
26532      * @return {Ext.Component} The previous node (or the previous node which matches the selector).
26533      * Returns null if there is no matching node.
26534      */
26535     previousNode: function(selector, includeSelf) {
26536         var node = this,
26537             result,
26538             it, len, i;
26539
26540         // If asked to include self, test me
26541         if (includeSelf && node.is(selector)) {
26542             return node;
26543         }
26544
26545         result = this.prev(selector);
26546         if (result) {
26547             return result;
26548         }
26549
26550         if (node.ownerCt) {
26551             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
26552                 if (it[i].query) {
26553                     result = it[i].query(selector);
26554                     result = result[result.length - 1];
26555                     if (result) {
26556                         return result;
26557                     }
26558                 }
26559             }
26560             return node.ownerCt.previousNode(selector, true);
26561         }
26562     },
26563
26564     /**
26565      * Returns the next node in the Component tree in tree traversal order.
26566      *
26567      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
26568      * tree to attempt to find a match. Contrast with {@link #nextSibling}.
26569      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
26570      * @return {Ext.Component} The next node (or the next node which matches the selector).
26571      * Returns null if there is no matching node.
26572      */
26573     nextNode: function(selector, includeSelf) {
26574         var node = this,
26575             result,
26576             it, len, i;
26577
26578         // If asked to include self, test me
26579         if (includeSelf && node.is(selector)) {
26580             return node;
26581         }
26582
26583         result = this.next(selector);
26584         if (result) {
26585             return result;
26586         }
26587
26588         if (node.ownerCt) {
26589             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
26590                 if (it[i].down) {
26591                     result = it[i].down(selector);
26592                     if (result) {
26593                         return result;
26594                     }
26595                 }
26596             }
26597             return node.ownerCt.nextNode(selector);
26598         }
26599     },
26600
26601     /**
26602      * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
26603      * @return {String}
26604      */
26605     getId : function() {
26606         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
26607     },
26608
26609     getItemId : function() {
26610         return this.itemId || this.id;
26611     },
26612
26613     /**
26614      * Retrieves the top level element representing this component.
26615      * @return {Ext.core.Element}
26616      */
26617     getEl : function() {
26618         return this.el;
26619     },
26620
26621     /**
26622      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
26623      * @private
26624      */
26625     getTargetEl: function() {
26626         return this.frameBody || this.el;
26627     },
26628
26629     /**
26630      * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
26631      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
26632      *
26633      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26634      * determination of inherited xtypes.**
26635      *
26636      * For a list of all available xtypes, see the {@link Ext.Component} header.
26637      *
26638      * Example usage:
26639      *
26640      *     var t = new Ext.form.field.Text();
26641      *     var isText = t.isXType('textfield');        // true
26642      *     var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
26643      *     var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
26644      *
26645      * @param {String} xtype The xtype to check for this Component
26646      * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
26647      * check whether this Component is descended from the xtype.
26648      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
26649      */
26650     isXType: function(xtype, shallow) {
26651         //assume a string by default
26652         if (Ext.isFunction(xtype)) {
26653             xtype = xtype.xtype;
26654             //handle being passed the class, e.g. Ext.Component
26655         } else if (Ext.isObject(xtype)) {
26656             xtype = xtype.statics().xtype;
26657             //handle being passed an instance
26658         }
26659
26660         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
26661     },
26662
26663     /**
26664      * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
26665      * {@link Ext.Component} header.
26666      *
26667      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26668      * determination of inherited xtypes.**
26669      *
26670      * Example usage:
26671      *
26672      *     var t = new Ext.form.field.Text();
26673      *     alert(t.getXTypes());  // alerts 'component/field/textfield'
26674      *
26675      * @return {String} The xtype hierarchy string
26676      */
26677     getXTypes: function() {
26678         var self = this.self,
26679             xtypes, parentPrototype, parentXtypes;
26680
26681         if (!self.xtypes) {
26682             xtypes = [];
26683             parentPrototype = this;
26684
26685             while (parentPrototype) {
26686                 parentXtypes = parentPrototype.xtypes;
26687
26688                 if (parentXtypes !== undefined) {
26689                     xtypes.unshift.apply(xtypes, parentXtypes);
26690                 }
26691
26692                 parentPrototype = parentPrototype.superclass;
26693             }
26694
26695             self.xtypeChain = xtypes;
26696             self.xtypes = xtypes.join('/');
26697         }
26698
26699         return self.xtypes;
26700     },
26701
26702     /**
26703      * Update the content area of a component.
26704      * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
26705      * it will use this argument as data to populate the template. If this component was not configured with a template,
26706      * the components content area will be updated via Ext.Element update
26707      * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
26708      * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
26709      * scripts have finished loading
26710      */
26711     update : function(htmlOrData, loadScripts, cb) {
26712         var me = this;
26713
26714         if (me.tpl && !Ext.isString(htmlOrData)) {
26715             me.data = htmlOrData;
26716             if (me.rendered) {
26717                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
26718             }
26719         } else {
26720             me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
26721             if (me.rendered) {
26722                 me.getTargetEl().update(me.html, loadScripts, cb);
26723             }
26724         }
26725
26726         if (me.rendered) {
26727             me.doComponentLayout();
26728         }
26729     },
26730
26731     /**
26732      * Convenience function to hide or show this component by boolean.
26733      * @param {Boolean} visible True to show, false to hide
26734      * @return {Ext.Component} this
26735      */
26736     setVisible : function(visible) {
26737         return this[visible ? 'show': 'hide']();
26738     },
26739
26740     /**
26741      * Returns true if this component is visible.
26742      *
26743      * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
26744      * determine whether this Component is truly visible to the user.
26745      *
26746      * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
26747      * dynamically laid out UIs in a hidden Container before showing them.
26748      *
26749      * @return {Boolean} True if this component is visible, false otherwise.
26750      */
26751     isVisible: function(deep) {
26752         var me = this,
26753             child = me,
26754             visible = !me.hidden,
26755             ancestor = me.ownerCt;
26756
26757         // Clear hiddenOwnerCt property
26758         me.hiddenAncestor = false;
26759         if (me.destroyed) {
26760             return false;
26761         }
26762
26763         if (deep && visible && me.rendered && ancestor) {
26764             while (ancestor) {
26765                 // If any ancestor is hidden, then this is hidden.
26766                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
26767                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
26768                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
26769                 if (ancestor.hidden || (ancestor.collapsed &&
26770                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
26771                     // Store hiddenOwnerCt property if needed
26772                     me.hiddenAncestor = ancestor;
26773                     visible = false;
26774                     break;
26775                 }
26776                 child = ancestor;
26777                 ancestor = ancestor.ownerCt;
26778             }
26779         }
26780         return visible;
26781     },
26782
26783     /**
26784      * Enable the component
26785      * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
26786      */
26787     enable: function(silent) {
26788         var me = this;
26789
26790         if (me.rendered) {
26791             me.el.removeCls(me.disabledCls);
26792             me.el.dom.disabled = false;
26793             me.onEnable();
26794         }
26795
26796         me.disabled = false;
26797
26798         if (silent !== true) {
26799             me.fireEvent('enable', me);
26800         }
26801
26802         return me;
26803     },
26804
26805     /**
26806      * Disable the component.
26807      * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
26808      */
26809     disable: function(silent) {
26810         var me = this;
26811
26812         if (me.rendered) {
26813             me.el.addCls(me.disabledCls);
26814             me.el.dom.disabled = true;
26815             me.onDisable();
26816         }
26817
26818         me.disabled = true;
26819
26820         if (silent !== true) {
26821             me.fireEvent('disable', me);
26822         }
26823
26824         return me;
26825     },
26826
26827     // @private
26828     onEnable: function() {
26829         if (this.maskOnDisable) {
26830             this.el.unmask();
26831         }
26832     },
26833
26834     // @private
26835     onDisable : function() {
26836         if (this.maskOnDisable) {
26837             this.el.mask();
26838         }
26839     },
26840
26841     /**
26842      * Method to determine whether this Component is currently disabled.
26843      * @return {Boolean} the disabled state of this Component.
26844      */
26845     isDisabled : function() {
26846         return this.disabled;
26847     },
26848
26849     /**
26850      * Enable or disable the component.
26851      * @param {Boolean} disabled True to disable.
26852      */
26853     setDisabled : function(disabled) {
26854         return this[disabled ? 'disable': 'enable']();
26855     },
26856
26857     /**
26858      * Method to determine whether this Component is currently set to hidden.
26859      * @return {Boolean} the hidden state of this Component.
26860      */
26861     isHidden : function() {
26862         return this.hidden;
26863     },
26864
26865     /**
26866      * Adds a CSS class to the top level element representing this component.
26867      * @param {String} cls The CSS class name to add
26868      * @return {Ext.Component} Returns the Component to allow method chaining.
26869      */
26870     addCls : function(className) {
26871         var me = this;
26872         if (!className) {
26873             return me;
26874         }
26875         if (!Ext.isArray(className)){
26876             className = className.replace(me.trimRe, '').split(me.spacesRe);
26877         }
26878         if (me.rendered) {
26879             me.el.addCls(className);
26880         }
26881         else {
26882             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
26883         }
26884         return me;
26885     },
26886
26887     /**
26888      * Adds a CSS class to the top level element representing this component.
26889      * @param {String} cls The CSS class name to add
26890      * @return {Ext.Component} Returns the Component to allow method chaining.
26891      */
26892     addClass : function() {
26893         return this.addCls.apply(this, arguments);
26894     },
26895
26896     /**
26897      * Removes a CSS class from the top level element representing this component.
26898      * @param {Object} className
26899      * @return {Ext.Component} Returns the Component to allow method chaining.
26900      */
26901     removeCls : function(className) {
26902         var me = this;
26903
26904         if (!className) {
26905             return me;
26906         }
26907         if (!Ext.isArray(className)){
26908             className = className.replace(me.trimRe, '').split(me.spacesRe);
26909         }
26910         if (me.rendered) {
26911             me.el.removeCls(className);
26912         }
26913         else if (me.additionalCls.length) {
26914             Ext.each(className, function(cls) {
26915                 Ext.Array.remove(me.additionalCls, cls);
26916             });
26917         }
26918         return me;
26919     },
26920
26921     removeClass : function() {
26922         if (Ext.isDefined(Ext.global.console)) {
26923             Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
26924         }
26925         return this.removeCls.apply(this, arguments);
26926     },
26927
26928     addOverCls: function() {
26929         var me = this;
26930         if (!me.disabled) {
26931             me.el.addCls(me.overCls);
26932         }
26933     },
26934
26935     removeOverCls: function() {
26936         this.el.removeCls(this.overCls);
26937     },
26938
26939     addListener : function(element, listeners, scope, options) {
26940         var me = this,
26941             fn,
26942             option;
26943
26944         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
26945             if (options.element) {
26946                 fn = listeners;
26947
26948                 listeners = {};
26949                 listeners[element] = fn;
26950                 element = options.element;
26951                 if (scope) {
26952                     listeners.scope = scope;
26953                 }
26954
26955                 for (option in options) {
26956                     if (options.hasOwnProperty(option)) {
26957                         if (me.eventOptionsRe.test(option)) {
26958                             listeners[option] = options[option];
26959                         }
26960                     }
26961                 }
26962             }
26963
26964             // At this point we have a variable called element,
26965             // and a listeners object that can be passed to on
26966             if (me[element] && me[element].on) {
26967                 me.mon(me[element], listeners);
26968             } else {
26969                 me.afterRenderEvents = me.afterRenderEvents || {};
26970                 if (!me.afterRenderEvents[element]) {
26971                     me.afterRenderEvents[element] = [];
26972                 }
26973                 me.afterRenderEvents[element].push(listeners);
26974             }
26975         }
26976
26977         return me.mixins.observable.addListener.apply(me, arguments);
26978     },
26979
26980     // inherit docs
26981     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
26982         var me = this,
26983             element = managedListener.options ? managedListener.options.element : null;
26984
26985         if (element) {
26986             element = me[element];
26987             if (element && element.un) {
26988                 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
26989                     element.un(managedListener.ename, managedListener.fn, managedListener.scope);
26990                     if (!isClear) {
26991                         Ext.Array.remove(me.managedListeners, managedListener);
26992                     }
26993                 }
26994             }
26995         } else {
26996             return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
26997         }
26998     },
26999
27000     /**
27001      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
27002      * @return {Ext.container.Container} the Container which owns this Component.
27003      */
27004     getBubbleTarget : function() {
27005         return this.ownerCt;
27006     },
27007
27008     /**
27009      * Method to determine whether this Component is floating.
27010      * @return {Boolean} the floating state of this component.
27011      */
27012     isFloating : function() {
27013         return this.floating;
27014     },
27015
27016     /**
27017      * Method to determine whether this Component is draggable.
27018      * @return {Boolean} the draggable state of this component.
27019      */
27020     isDraggable : function() {
27021         return !!this.draggable;
27022     },
27023
27024     /**
27025      * Method to determine whether this Component is droppable.
27026      * @return {Boolean} the droppable state of this component.
27027      */
27028     isDroppable : function() {
27029         return !!this.droppable;
27030     },
27031
27032     /**
27033      * @private
27034      * Method to manage awareness of when components are added to their
27035      * respective Container, firing an added event.
27036      * References are established at add time rather than at render time.
27037      * @param {Ext.container.Container} container Container which holds the component
27038      * @param {Number} pos Position at which the component was added
27039      */
27040     onAdded : function(container, pos) {
27041         this.ownerCt = container;
27042         this.fireEvent('added', this, container, pos);
27043     },
27044
27045     /**
27046      * @private
27047      * Method to manage awareness of when components are removed from their
27048      * respective Container, firing an removed event. References are properly
27049      * cleaned up after removing a component from its owning container.
27050      */
27051     onRemoved : function() {
27052         var me = this;
27053
27054         me.fireEvent('removed', me, me.ownerCt);
27055         delete me.ownerCt;
27056     },
27057
27058     // @private
27059     beforeDestroy : Ext.emptyFn,
27060     // @private
27061     // @private
27062     onResize : Ext.emptyFn,
27063
27064     /**
27065      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
27066      * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
27067      *
27068      * @param {Number/String/Object} width The new width to set. This may be one of:
27069      *
27070      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27071      *   - A String used to set the CSS width style.
27072      *   - A size object in the format `{width: widthValue, height: heightValue}`.
27073      *   - `undefined` to leave the width unchanged.
27074      *
27075      * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
27076      * This may be one of:
27077      *
27078      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27079      *   - A String used to set the CSS height style. Animation may **not** be used.
27080      *   - `undefined` to leave the height unchanged.
27081      *
27082      * @return {Ext.Component} this
27083      */
27084     setSize : function(width, height) {
27085         var me = this,
27086             layoutCollection;
27087
27088         // support for standard size objects
27089         if (Ext.isObject(width)) {
27090             height = width.height;
27091             width  = width.width;
27092         }
27093
27094         // Constrain within configured maxima
27095         if (Ext.isNumber(width)) {
27096             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
27097         }
27098         if (Ext.isNumber(height)) {
27099             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
27100         }
27101
27102         if (!me.rendered || !me.isVisible()) {
27103             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
27104             if (me.hiddenAncestor) {
27105                 layoutCollection = me.hiddenAncestor.layoutOnShow;
27106                 layoutCollection.remove(me);
27107                 layoutCollection.add(me);
27108             }
27109             me.needsLayout = {
27110                 width: width,
27111                 height: height,
27112                 isSetSize: true
27113             };
27114             if (!me.rendered) {
27115                 me.width  = (width !== undefined) ? width : me.width;
27116                 me.height = (height !== undefined) ? height : me.height;
27117             }
27118             return me;
27119         }
27120         me.doComponentLayout(width, height, true);
27121
27122         return me;
27123     },
27124
27125     isFixedWidth: function() {
27126         var me = this,
27127             layoutManagedWidth = me.layoutManagedWidth;
27128
27129         if (Ext.isDefined(me.width) || layoutManagedWidth == 1) {
27130             return true;
27131         }
27132         if (layoutManagedWidth == 2) {
27133             return false;
27134         }
27135         return (me.ownerCt && me.ownerCt.isFixedWidth());
27136     },
27137
27138     isFixedHeight: function() {
27139         var me = this,
27140             layoutManagedHeight = me.layoutManagedHeight;
27141
27142         if (Ext.isDefined(me.height) || layoutManagedHeight == 1) {
27143             return true;
27144         }
27145         if (layoutManagedHeight == 2) {
27146             return false;
27147         }
27148         return (me.ownerCt && me.ownerCt.isFixedHeight());
27149     },
27150
27151     setCalculatedSize : function(width, height, callingContainer) {
27152         var me = this,
27153             layoutCollection;
27154
27155         // support for standard size objects
27156         if (Ext.isObject(width)) {
27157             callingContainer = width.ownerCt;
27158             height = width.height;
27159             width  = width.width;
27160         }
27161
27162         // Constrain within configured maxima
27163         if (Ext.isNumber(width)) {
27164             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
27165         }
27166         if (Ext.isNumber(height)) {
27167             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
27168         }
27169
27170         if (!me.rendered || !me.isVisible()) {
27171             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
27172             if (me.hiddenAncestor) {
27173                 layoutCollection = me.hiddenAncestor.layoutOnShow;
27174                 layoutCollection.remove(me);
27175                 layoutCollection.add(me);
27176             }
27177             me.needsLayout = {
27178                 width: width,
27179                 height: height,
27180                 isSetSize: false,
27181                 ownerCt: callingContainer
27182             };
27183             return me;
27184         }
27185         me.doComponentLayout(width, height, false, callingContainer);
27186
27187         return me;
27188     },
27189
27190     /**
27191      * This method needs to be called whenever you change something on this component that requires the Component's
27192      * layout to be recalculated.
27193      * @param {Object} width
27194      * @param {Object} height
27195      * @param {Object} isSetSize
27196      * @param {Object} callingContainer
27197      * @return {Ext.container.Container} this
27198      */
27199     doComponentLayout : function(width, height, isSetSize, callingContainer) {
27200         var me = this,
27201             componentLayout = me.getComponentLayout(),
27202             lastComponentSize = componentLayout.lastComponentSize || {
27203                 width: undefined,
27204                 height: undefined
27205             };
27206
27207         // collapsed state is not relevant here, so no testing done.
27208         // Only Panels have a collapse method, and that just sets the width/height such that only
27209         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
27210         if (me.rendered && componentLayout) {
27211             // If no width passed, then only insert a value if the Component is NOT ALLOWED to autowidth itself.
27212             if (!Ext.isDefined(width)) {
27213                 if (me.isFixedWidth()) {
27214                     width = Ext.isDefined(me.width) ? me.width : lastComponentSize.width;
27215                 }
27216             }
27217             // If no height passed, then only insert a value if the Component is NOT ALLOWED to autoheight itself.
27218             if (!Ext.isDefined(height)) {
27219                 if (me.isFixedHeight()) {
27220                     height = Ext.isDefined(me.height) ? me.height : lastComponentSize.height;
27221                 }
27222             }
27223
27224             if (isSetSize) {
27225                 me.width = width;
27226                 me.height = height;
27227             }
27228
27229             componentLayout.layout(width, height, isSetSize, callingContainer);
27230         }
27231
27232         return me;
27233     },
27234
27235     /**
27236      * Forces this component to redo its componentLayout.
27237      */
27238     forceComponentLayout: function () {
27239         this.doComponentLayout();
27240     },
27241
27242     // @private
27243     setComponentLayout : function(layout) {
27244         var currentLayout = this.componentLayout;
27245         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
27246             currentLayout.setOwner(null);
27247         }
27248         this.componentLayout = layout;
27249         layout.setOwner(this);
27250     },
27251
27252     getComponentLayout : function() {
27253         var me = this;
27254
27255         if (!me.componentLayout || !me.componentLayout.isLayout) {
27256             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
27257         }
27258         return me.componentLayout;
27259     },
27260
27261     /**
27262      * Occurs after componentLayout is run.
27263      * @param {Number} adjWidth The box-adjusted width that was set
27264      * @param {Number} adjHeight The box-adjusted height that was set
27265      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
27266      * @param {Ext.Component} callingContainer Container requesting the layout. Only used when isSetSize is false.
27267      */
27268     afterComponentLayout: function(width, height, isSetSize, callingContainer) {
27269         var me = this,
27270             layout = me.componentLayout,
27271             oldSize = me.preLayoutSize;
27272
27273         ++me.componentLayoutCounter;
27274         if (!oldSize || ((width !== oldSize.width) || (height !== oldSize.height))) {
27275             me.fireEvent('resize', me, width, height);
27276         }
27277     },
27278
27279     /**
27280      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
27281      * being executed.
27282      * @param {Number} adjWidth The box-adjusted width that was set
27283      * @param {Number} adjHeight The box-adjusted height that was set
27284      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
27285      * @param {Ext.Component} callingContainer Container requesting sent the layout. Only used when isSetSize is false.
27286      */
27287     beforeComponentLayout: function(width, height, isSetSize, callingContainer) {
27288         this.preLayoutSize = this.componentLayout.lastComponentSize;
27289         return true;
27290     },
27291
27292     /**
27293      * Sets the left and top of the component. To set the page XY position instead, use
27294      * {@link Ext.Component#setPagePosition setPagePosition}. This method fires the {@link #move} event.
27295      * @param {Number} left The new left
27296      * @param {Number} top The new top
27297      * @return {Ext.Component} this
27298      */
27299     setPosition : function(x, y) {
27300         var me = this;
27301
27302         if (Ext.isObject(x)) {
27303             y = x.y;
27304             x = x.x;
27305         }
27306
27307         if (!me.rendered) {
27308             return me;
27309         }
27310
27311         if (x !== undefined || y !== undefined) {
27312             me.el.setBox(x, y);
27313             me.onPosition(x, y);
27314             me.fireEvent('move', me, x, y);
27315         }
27316         return me;
27317     },
27318
27319     /**
27320      * @private
27321      * Called after the component is moved, this method is empty by default but can be implemented by any
27322      * subclass that needs to perform custom logic after a move occurs.
27323      * @param {Number} x The new x position
27324      * @param {Number} y The new y position
27325      */
27326     onPosition: Ext.emptyFn,
27327
27328     /**
27329      * Sets the width of the component. This method fires the {@link #resize} event.
27330      *
27331      * @param {Number} width The new width to setThis may be one of:
27332      *
27333      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27334      *   - A String used to set the CSS width style.
27335      *
27336      * @return {Ext.Component} this
27337      */
27338     setWidth : function(width) {
27339         return this.setSize(width);
27340     },
27341
27342     /**
27343      * Sets the height of the component. This method fires the {@link #resize} event.
27344      *
27345      * @param {Number} height The new height to set. This may be one of:
27346      *
27347      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
27348      *   - A String used to set the CSS height style.
27349      *   - _undefined_ to leave the height unchanged.
27350      *
27351      * @return {Ext.Component} this
27352      */
27353     setHeight : function(height) {
27354         return this.setSize(undefined, height);
27355     },
27356
27357     /**
27358      * Gets the current size of the component's underlying element.
27359      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
27360      */
27361     getSize : function() {
27362         return this.el.getSize();
27363     },
27364
27365     /**
27366      * Gets the current width of the component's underlying element.
27367      * @return {Number}
27368      */
27369     getWidth : function() {
27370         return this.el.getWidth();
27371     },
27372
27373     /**
27374      * Gets the current height of the component's underlying element.
27375      * @return {Number}
27376      */
27377     getHeight : function() {
27378         return this.el.getHeight();
27379     },
27380
27381     /**
27382      * Gets the {@link Ext.ComponentLoader} for this Component.
27383      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
27384      */
27385     getLoader: function(){
27386         var me = this,
27387             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
27388             loader = me.loader || autoLoad;
27389
27390         if (loader) {
27391             if (!loader.isLoader) {
27392                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
27393                     target: me,
27394                     autoLoad: autoLoad
27395                 }, loader));
27396             } else {
27397                 loader.setTarget(me);
27398             }
27399             return me.loader;
27400
27401         }
27402         return null;
27403     },
27404
27405     /**
27406      * This method allows you to show or hide a LoadMask on top of this component.
27407      *
27408      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
27409      * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
27410      * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
27411      * setting this to true on a Panel will cause only the body to be masked.
27412      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
27413      */
27414     setLoading : function(load, targetEl) {
27415         var me = this,
27416             config;
27417
27418         if (me.rendered) {
27419             if (load !== false && !me.collapsed) {
27420                 if (Ext.isObject(load)) {
27421                     config = load;
27422                 }
27423                 else if (Ext.isString(load)) {
27424                     config = {msg: load};
27425                 }
27426                 else {
27427                     config = {};
27428                 }
27429                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
27430                 me.loadMask.show();
27431             } else if (me.loadMask) {
27432                 Ext.destroy(me.loadMask);
27433                 me.loadMask = null;
27434             }
27435         }
27436
27437         return me.loadMask;
27438     },
27439
27440     /**
27441      * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
27442      * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
27443      * @param {Object} dock The dock position.
27444      * @param {Boolean} [layoutParent=false] True to re-layout parent.
27445      * @return {Ext.Component} this
27446      */
27447     setDocked : function(dock, layoutParent) {
27448         var me = this;
27449
27450         me.dock = dock;
27451         if (layoutParent && me.ownerCt && me.rendered) {
27452             me.ownerCt.doComponentLayout();
27453         }
27454         return me;
27455     },
27456
27457     onDestroy : function() {
27458         var me = this;
27459
27460         if (me.monitorResize && Ext.EventManager.resizeEvent) {
27461             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
27462         }
27463         // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
27464         Ext.destroy(
27465             me.componentLayout,
27466             me.loadMask,
27467             me.floatingItems
27468         );
27469     },
27470
27471     /**
27472      * Remove any references to elements added via renderSelectors/childEls
27473      * @private
27474      */
27475     cleanElementRefs: function(){
27476         var me = this,
27477             i = 0,
27478             childEls = me.childEls,
27479             selectors = me.renderSelectors,
27480             selector,
27481             name,
27482             len;
27483
27484         if (me.rendered) {
27485             if (childEls) {
27486                 for (len = childEls.length; i < len; ++i) {
27487                     name = childEls[i];
27488                     if (typeof(name) != 'string') {
27489                         name = name.name;
27490                     }
27491                     delete me[name];
27492                 }
27493             }
27494
27495             if (selectors) {
27496                 for (selector in selectors) {
27497                     if (selectors.hasOwnProperty(selector)) {
27498                         delete me[selector];
27499                     }
27500                 }
27501             }
27502         }
27503         delete me.rendered;
27504         delete me.el;
27505         delete me.frameBody;
27506     },
27507
27508     /**
27509      * Destroys the Component.
27510      */
27511     destroy : function() {
27512         var me = this;
27513
27514         if (!me.isDestroyed) {
27515             if (me.fireEvent('beforedestroy', me) !== false) {
27516                 me.destroying = true;
27517                 me.beforeDestroy();
27518
27519                 if (me.floating) {
27520                     delete me.floatParent;
27521                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
27522                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
27523                     if (me.zIndexManager) {
27524                         me.zIndexManager.unregister(me);
27525                     }
27526                 } else if (me.ownerCt && me.ownerCt.remove) {
27527                     me.ownerCt.remove(me, false);
27528                 }
27529
27530                 me.onDestroy();
27531
27532                 // Attempt to destroy all plugins
27533                 Ext.destroy(me.plugins);
27534
27535                 if (me.rendered) {
27536                     me.el.remove();
27537                 }
27538
27539                 me.fireEvent('destroy', me);
27540                 Ext.ComponentManager.unregister(me);
27541
27542                 me.mixins.state.destroy.call(me);
27543
27544                 me.clearListeners();
27545                 // make sure we clean up the element references after removing all events
27546                 me.cleanElementRefs();
27547                 me.destroying = false;
27548                 me.isDestroyed = true;
27549             }
27550         }
27551     },
27552
27553     /**
27554      * Retrieves a plugin by its pluginId which has been bound to this component.
27555      * @param {Object} pluginId
27556      * @return {Ext.AbstractPlugin} plugin instance.
27557      */
27558     getPlugin: function(pluginId) {
27559         var i = 0,
27560             plugins = this.plugins,
27561             ln = plugins.length;
27562         for (; i < ln; i++) {
27563             if (plugins[i].pluginId === pluginId) {
27564                 return plugins[i];
27565             }
27566         }
27567     },
27568
27569     /**
27570      * Determines whether this component is the descendant of a particular container.
27571      * @param {Ext.Container} container
27572      * @return {Boolean} True if it is.
27573      */
27574     isDescendantOf: function(container) {
27575         return !!this.findParentBy(function(p){
27576             return p === container;
27577         });
27578     }
27579 }, function() {
27580     this.createAlias({
27581         on: 'addListener',
27582         prev: 'previousSibling',
27583         next: 'nextSibling'
27584     });
27585 });
27586
27587 /**
27588  * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
27589  *
27590  * This class defines the essential API of plugins as used by Components by defining the following methods:
27591  *
27592  *   - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
27593  *
27594  *     The Component passes itself as the sole parameter.
27595  *
27596  *     Subclasses should set up bidirectional links between the plugin and its client Component here.
27597  *
27598  *   - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
27599  *
27600  *     Use this method to break links between the plugin and the Component and to free any allocated resources.
27601  *
27602  *   - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
27603  *
27604  *   - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
27605  */
27606 Ext.define('Ext.AbstractPlugin', {
27607     disabled: false,
27608
27609     constructor: function(config) {
27610         if (!config.cmp && Ext.global.console) {
27611             Ext.global.console.warn("Attempted to attach a plugin ");
27612         }
27613         Ext.apply(this, config);
27614     },
27615
27616     getCmp: function() {
27617         return this.cmp;
27618     },
27619
27620     /**
27621      * @method
27622      * The init method is invoked after initComponent method has been run for the client Component.
27623      *
27624      * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
27625      * links between the plugin and its client Component in their own implementation of this method.
27626      * @param {Ext.Component} client The client Component which owns this plugin.
27627      */
27628     init: Ext.emptyFn,
27629
27630     /**
27631      * @method
27632      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
27633      *
27634      * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
27635      * this method.
27636      */
27637     destroy: Ext.emptyFn,
27638
27639     /**
27640      * The base implementation just sets the plugin's `disabled` flag to `false`
27641      *
27642      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27643      */
27644     enable: function() {
27645         this.disabled = false;
27646     },
27647
27648     /**
27649      * The base implementation just sets the plugin's `disabled` flag to `true`
27650      *
27651      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27652      */
27653     disable: function() {
27654         this.disabled = true;
27655     }
27656 });
27657 /**
27658  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
27659  * to a configured URL, or to a URL specified at request time.
27660  *
27661  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
27662  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
27663  * in the request options object, or an {@link #requestcomplete event listener}.
27664  *
27665  * # File Uploads
27666  *
27667  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
27668  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
27669  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
27670  * after the return data has been gathered.
27671  *
27672  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
27673  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
27674  * insert the text unchanged into the document body.
27675  *
27676  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
27677  * `&amp;` etc.
27678  *
27679  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
27680  * responseText property in order to conform to the requirements of event handlers and callbacks.
27681  *
27682  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
27683  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
27684  * packet content.
27685  *
27686  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
27687  */
27688 Ext.define('Ext.data.Connection', {
27689     mixins: {
27690         observable: 'Ext.util.Observable'
27691     },
27692
27693     statics: {
27694         requestId: 0
27695     },
27696
27697     url: null,
27698     async: true,
27699     method: null,
27700     username: '',
27701     password: '',
27702
27703     /**
27704      * @cfg {Boolean} disableCaching
27705      * True to add a unique cache-buster param to GET requests.
27706      */
27707     disableCaching: true,
27708
27709     /**
27710      * @cfg {Boolean} withCredentials
27711      * True to set `withCredentials = true` on the XHR object
27712      */
27713     withCredentials: false,
27714
27715     /**
27716      * @cfg {Boolean} cors
27717      * True to enable CORS support on the XHR object. Currently the only effect of this option
27718      * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
27719      */
27720     cors: false,
27721
27722     /**
27723      * @cfg {String} disableCachingParam
27724      * Change the parameter which is sent went disabling caching through a cache buster.
27725      */
27726     disableCachingParam: '_dc',
27727
27728     /**
27729      * @cfg {Number} timeout
27730      * The timeout in milliseconds to be used for requests.
27731      */
27732     timeout : 30000,
27733
27734     /**
27735      * @cfg {Object} extraParams
27736      * Any parameters to be appended to the request.
27737      */
27738
27739     useDefaultHeader : true,
27740     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
27741     useDefaultXhrHeader : true,
27742     defaultXhrHeader : 'XMLHttpRequest',
27743
27744     constructor : function(config) {
27745         config = config || {};
27746         Ext.apply(this, config);
27747
27748         this.addEvents(
27749             /**
27750              * @event beforerequest
27751              * Fires before a network request is made to retrieve a data object.
27752              * @param {Ext.data.Connection} conn This Connection object.
27753              * @param {Object} options The options config object passed to the {@link #request} method.
27754              */
27755             'beforerequest',
27756             /**
27757              * @event requestcomplete
27758              * Fires if the request was successfully completed.
27759              * @param {Ext.data.Connection} conn This Connection object.
27760              * @param {Object} response The XHR object containing the response data.
27761              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27762              * @param {Object} options The options config object passed to the {@link #request} method.
27763              */
27764             'requestcomplete',
27765             /**
27766              * @event requestexception
27767              * Fires if an error HTTP status was returned from the server.
27768              * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
27769              * for details of HTTP status codes.
27770              * @param {Ext.data.Connection} conn This Connection object.
27771              * @param {Object} response The XHR object containing the response data.
27772              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27773              * @param {Object} options The options config object passed to the {@link #request} method.
27774              */
27775             'requestexception'
27776         );
27777         this.requests = {};
27778         this.mixins.observable.constructor.call(this);
27779     },
27780
27781     /**
27782      * Sends an HTTP request to a remote server.
27783      *
27784      * **Important:** Ajax server requests are asynchronous, and this call will
27785      * return before the response has been received. Process any returned data
27786      * in a callback function.
27787      *
27788      *     Ext.Ajax.request({
27789      *         url: 'ajax_demo/sample.json',
27790      *         success: function(response, opts) {
27791      *             var obj = Ext.decode(response.responseText);
27792      *             console.dir(obj);
27793      *         },
27794      *         failure: function(response, opts) {
27795      *             console.log('server-side failure with status code ' + response.status);
27796      *         }
27797      *     });
27798      *
27799      * To execute a callback function in the correct scope, use the `scope` option.
27800      *
27801      * @param {Object} options An object which may contain the following properties:
27802      *
27803      * (The options object may also contain any other property which might be needed to perform
27804      * postprocessing in a callback because it is passed to callback functions.)
27805      *
27806      * @param {String/Function} options.url The URL to which to send the request, or a function
27807      * to call which returns a URL string. The scope of the function is specified by the `scope` option.
27808      * Defaults to the configured `url`.
27809      *
27810      * @param {Object/String/Function} options.params An object containing properties which are
27811      * used as parameters to the request, a url encoded string or a function to call to get either. The scope
27812      * of the function is specified by the `scope` option.
27813      *
27814      * @param {String} options.method The HTTP method to use
27815      * for the request. Defaults to the configured method, or if no method was configured,
27816      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
27817      * the method name is case-sensitive and should be all caps.
27818      *
27819      * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
27820      * The callback is called regardless of success or failure and is passed the following parameters:
27821      * @param {Object} options.callback.options The parameter to the request call.
27822      * @param {Boolean} options.callback.success True if the request succeeded.
27823      * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
27824      * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
27825      * accessing elements of the response.
27826      *
27827      * @param {Function} options.success The function to be called upon success of the request.
27828      * The callback is passed the following parameters:
27829      * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
27830      * @param {Object} options.success.options The parameter to the request call.
27831      *
27832      * @param {Function} options.failure The function to be called upon success of the request.
27833      * The callback is passed the following parameters:
27834      * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
27835      * @param {Object} options.failure.options The parameter to the request call.
27836      *
27837      * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
27838      * the callback function. If the `url`, or `params` options were specified as functions from which to
27839      * draw values, then this also serves as the scope for those function calls. Defaults to the browser
27840      * window.
27841      *
27842      * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
27843      * Defaults to 30 seconds.
27844      *
27845      * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
27846      * to pull parameters from.
27847      *
27848      * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
27849      *
27850      * True if the form object is a file upload (will be set automatically if the form was configured
27851      * with **`enctype`** `"multipart/form-data"`).
27852      *
27853      * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
27854      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
27855      * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
27856      * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
27857      * has been gathered.
27858      *
27859      * The server response is parsed by the browser to create the document for the IFRAME. If the
27860      * server is using JSON to send the return object, then the [Content-Type][] header must be set to
27861      * "text/html" in order to tell the browser to insert the text unchanged into the document body.
27862      *
27863      * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
27864      * containing a `responseText` property in order to conform to the requirements of event handlers
27865      * and callbacks.
27866      *
27867      * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
27868      * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
27869      * and parameter values from the packet content.
27870      *
27871      * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
27872      * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
27873      * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
27874      *
27875      * @param {Object} options.headers Request headers to set for the request.
27876      *
27877      * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
27878      * of params for the post data. Any params will be appended to the URL.
27879      *
27880      * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
27881      * instead of params for the post data. Any params will be appended to the URL.
27882      *
27883      * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
27884      *
27885      * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
27886      *
27887      * @return {Object} The request object. This may be used to cancel the request.
27888      */
27889     request : function(options) {
27890         options = options || {};
27891         var me = this,
27892             scope = options.scope || window,
27893             username = options.username || me.username,
27894             password = options.password || me.password || '',
27895             async,
27896             requestOptions,
27897             request,
27898             headers,
27899             xhr;
27900
27901         if (me.fireEvent('beforerequest', me, options) !== false) {
27902
27903             requestOptions = me.setOptions(options, scope);
27904
27905             if (this.isFormUpload(options) === true) {
27906                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
27907                 return null;
27908             }
27909
27910             // if autoabort is set, cancel the current transactions
27911             if (options.autoAbort === true || me.autoAbort) {
27912                 me.abort();
27913             }
27914
27915             // create a connection object
27916
27917             if ((options.cors === true || me.cors === true) && Ext.isIe && Ext.ieVersion >= 8) {
27918                 xhr = new XDomainRequest();
27919             } else {
27920                 xhr = this.getXhrInstance();
27921             }
27922
27923             async = options.async !== false ? (options.async || me.async) : false;
27924
27925             // open the request
27926             if (username) {
27927                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
27928             } else {
27929                 xhr.open(requestOptions.method, requestOptions.url, async);
27930             }
27931
27932             if (options.withCredentials === true || me.withCredentials === true) {
27933                 xhr.withCredentials = true;
27934             }
27935
27936             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
27937
27938             // create the transaction object
27939             request = {
27940                 id: ++Ext.data.Connection.requestId,
27941                 xhr: xhr,
27942                 headers: headers,
27943                 options: options,
27944                 async: async,
27945                 timeout: setTimeout(function() {
27946                     request.timedout = true;
27947                     me.abort(request);
27948                 }, options.timeout || me.timeout)
27949             };
27950             me.requests[request.id] = request;
27951             me.latestId = request.id;
27952             // bind our statechange listener
27953             if (async) {
27954                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
27955             }
27956
27957             // start the request!
27958             xhr.send(requestOptions.data);
27959             if (!async) {
27960                 return this.onComplete(request);
27961             }
27962             return request;
27963         } else {
27964             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
27965             return null;
27966         }
27967     },
27968
27969     /**
27970      * Uploads a form using a hidden iframe.
27971      * @param {String/HTMLElement/Ext.Element} form The form to upload
27972      * @param {String} url The url to post to
27973      * @param {String} params Any extra parameters to pass
27974      * @param {Object} options The initial options
27975      */
27976     upload: function(form, url, params, options) {
27977         form = Ext.getDom(form);
27978         options = options || {};
27979
27980         var id = Ext.id(),
27981                 frame = document.createElement('iframe'),
27982                 hiddens = [],
27983                 encoding = 'multipart/form-data',
27984                 buf = {
27985                     target: form.target,
27986                     method: form.method,
27987                     encoding: form.encoding,
27988                     enctype: form.enctype,
27989                     action: form.action
27990                 }, hiddenItem;
27991
27992         /*
27993          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
27994          * the frame had been added to the document. It seems this has since been corrected in
27995          * Opera so the behaviour has been reverted, the URL will be set before being added.
27996          */
27997         Ext.fly(frame).set({
27998             id: id,
27999             name: id,
28000             cls: Ext.baseCSSPrefix + 'hide-display',
28001             src: Ext.SSL_SECURE_URL
28002         });
28003
28004         document.body.appendChild(frame);
28005
28006         // This is required so that IE doesn't pop the response up in a new window.
28007         if (document.frames) {
28008            document.frames[id].name = id;
28009         }
28010
28011         Ext.fly(form).set({
28012             target: id,
28013             method: 'POST',
28014             enctype: encoding,
28015             encoding: encoding,
28016             action: url || buf.action
28017         });
28018
28019         // add dynamic params
28020         if (params) {
28021             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
28022                 hiddenItem = document.createElement('input');
28023                 Ext.fly(hiddenItem).set({
28024                     type: 'hidden',
28025                     value: value,
28026                     name: name
28027                 });
28028                 form.appendChild(hiddenItem);
28029                 hiddens.push(hiddenItem);
28030             });
28031         }
28032
28033         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
28034         form.submit();
28035
28036         Ext.fly(form).set(buf);
28037         Ext.each(hiddens, function(h) {
28038             Ext.removeNode(h);
28039         });
28040     },
28041
28042     /**
28043      * @private
28044      * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
28045      * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
28046      * (or a textarea inside the body). We then clean up by removing the iframe
28047      */
28048     onUploadComplete: function(frame, options) {
28049         var me = this,
28050             // bogus response object
28051             response = {
28052                 responseText: '',
28053                 responseXML: null
28054             }, doc, firstChild;
28055
28056         try {
28057             doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
28058             if (doc) {
28059                 if (doc.body) {
28060                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
28061                         response.responseText = firstChild.value;
28062                     } else {
28063                         response.responseText = doc.body.innerHTML;
28064                     }
28065                 }
28066                 //in IE the document may still have a body even if returns XML.
28067                 response.responseXML = doc.XMLDocument || doc;
28068             }
28069         } catch (e) {
28070         }
28071
28072         me.fireEvent('requestcomplete', me, response, options);
28073
28074         Ext.callback(options.success, options.scope, [response, options]);
28075         Ext.callback(options.callback, options.scope, [options, true, response]);
28076
28077         setTimeout(function(){
28078             Ext.removeNode(frame);
28079         }, 100);
28080     },
28081
28082     /**
28083      * Detects whether the form is intended to be used for an upload.
28084      * @private
28085      */
28086     isFormUpload: function(options){
28087         var form = this.getForm(options);
28088         if (form) {
28089             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
28090         }
28091         return false;
28092     },
28093
28094     /**
28095      * Gets the form object from options.
28096      * @private
28097      * @param {Object} options The request options
28098      * @return {HTMLElement} The form, null if not passed
28099      */
28100     getForm: function(options){
28101         return Ext.getDom(options.form) || null;
28102     },
28103
28104     /**
28105      * Sets various options such as the url, params for the request
28106      * @param {Object} options The initial options
28107      * @param {Object} scope The scope to execute in
28108      * @return {Object} The params for the request
28109      */
28110     setOptions: function(options, scope){
28111         var me =  this,
28112             params = options.params || {},
28113             extraParams = me.extraParams,
28114             urlParams = options.urlParams,
28115             url = options.url || me.url,
28116             jsonData = options.jsonData,
28117             method,
28118             disableCache,
28119             data;
28120
28121
28122         // allow params to be a method that returns the params object
28123         if (Ext.isFunction(params)) {
28124             params = params.call(scope, options);
28125         }
28126
28127         // allow url to be a method that returns the actual url
28128         if (Ext.isFunction(url)) {
28129             url = url.call(scope, options);
28130         }
28131
28132         url = this.setupUrl(options, url);
28133
28134         if (!url) {
28135             Ext.Error.raise({
28136                 options: options,
28137                 msg: 'No URL specified'
28138             });
28139         }
28140
28141         // check for xml or json data, and make sure json data is encoded
28142         data = options.rawData || options.xmlData || jsonData || null;
28143         if (jsonData && !Ext.isPrimitive(jsonData)) {
28144             data = Ext.encode(data);
28145         }
28146
28147         // make sure params are a url encoded string and include any extraParams if specified
28148         if (Ext.isObject(params)) {
28149             params = Ext.Object.toQueryString(params);
28150         }
28151
28152         if (Ext.isObject(extraParams)) {
28153             extraParams = Ext.Object.toQueryString(extraParams);
28154         }
28155
28156         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
28157
28158         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
28159
28160         params = this.setupParams(options, params);
28161
28162         // decide the proper method for this request
28163         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
28164         this.setupMethod(options, method);
28165
28166
28167         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
28168         // if the method is get append date to prevent caching
28169         if (method === 'GET' && disableCache) {
28170             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
28171         }
28172
28173         // if the method is get or there is json/xml data append the params to the url
28174         if ((method == 'GET' || data) && params) {
28175             url = Ext.urlAppend(url, params);
28176             params = null;
28177         }
28178
28179         // allow params to be forced into the url
28180         if (urlParams) {
28181             url = Ext.urlAppend(url, urlParams);
28182         }
28183
28184         return {
28185             url: url,
28186             method: method,
28187             data: data || params || null
28188         };
28189     },
28190
28191     /**
28192      * Template method for overriding url
28193      * @template
28194      * @private
28195      * @param {Object} options
28196      * @param {String} url
28197      * @return {String} The modified url
28198      */
28199     setupUrl: function(options, url){
28200         var form = this.getForm(options);
28201         if (form) {
28202             url = url || form.action;
28203         }
28204         return url;
28205     },
28206
28207
28208     /**
28209      * Template method for overriding params
28210      * @template
28211      * @private
28212      * @param {Object} options
28213      * @param {String} params
28214      * @return {String} The modified params
28215      */
28216     setupParams: function(options, params) {
28217         var form = this.getForm(options),
28218             serializedForm;
28219         if (form && !this.isFormUpload(options)) {
28220             serializedForm = Ext.Element.serializeForm(form);
28221             params = params ? (params + '&' + serializedForm) : serializedForm;
28222         }
28223         return params;
28224     },
28225
28226     /**
28227      * Template method for overriding method
28228      * @template
28229      * @private
28230      * @param {Object} options
28231      * @param {String} method
28232      * @return {String} The modified method
28233      */
28234     setupMethod: function(options, method){
28235         if (this.isFormUpload(options)) {
28236             return 'POST';
28237         }
28238         return method;
28239     },
28240
28241     /**
28242      * Setup all the headers for the request
28243      * @private
28244      * @param {Object} xhr The xhr object
28245      * @param {Object} options The options for the request
28246      * @param {Object} data The data for the request
28247      * @param {Object} params The params for the request
28248      */
28249     setupHeaders: function(xhr, options, data, params){
28250         var me = this,
28251             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
28252             contentType = me.defaultPostHeader,
28253             jsonData = options.jsonData,
28254             xmlData = options.xmlData,
28255             key,
28256             header;
28257
28258         if (!headers['Content-Type'] && (data || params)) {
28259             if (data) {
28260                 if (options.rawData) {
28261                     contentType = 'text/plain';
28262                 } else {
28263                     if (xmlData && Ext.isDefined(xmlData)) {
28264                         contentType = 'text/xml';
28265                     } else if (jsonData && Ext.isDefined(jsonData)) {
28266                         contentType = 'application/json';
28267                     }
28268                 }
28269             }
28270             headers['Content-Type'] = contentType;
28271         }
28272
28273         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
28274             headers['X-Requested-With'] = me.defaultXhrHeader;
28275         }
28276         // set up all the request headers on the xhr object
28277         try{
28278             for (key in headers) {
28279                 if (headers.hasOwnProperty(key)) {
28280                     header = headers[key];
28281                     xhr.setRequestHeader(key, header);
28282                 }
28283
28284             }
28285         } catch(e) {
28286             me.fireEvent('exception', key, header);
28287         }
28288         return headers;
28289     },
28290
28291     /**
28292      * Creates the appropriate XHR transport for the browser.
28293      * @private
28294      */
28295     getXhrInstance: (function(){
28296         var options = [function(){
28297             return new XMLHttpRequest();
28298         }, function(){
28299             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
28300         }, function(){
28301             return new ActiveXObject('MSXML2.XMLHTTP');
28302         }, function(){
28303             return new ActiveXObject('Microsoft.XMLHTTP');
28304         }], i = 0,
28305             len = options.length,
28306             xhr;
28307
28308         for(; i < len; ++i) {
28309             try{
28310                 xhr = options[i];
28311                 xhr();
28312                 break;
28313             }catch(e){}
28314         }
28315         return xhr;
28316     })(),
28317
28318     /**
28319      * Determines whether this object has a request outstanding.
28320      * @param {Object} [request] Defaults to the last transaction
28321      * @return {Boolean} True if there is an outstanding request.
28322      */
28323     isLoading : function(request) {
28324         if (!request) {
28325             request = this.getLatest();
28326         }
28327         if (!(request && request.xhr)) {
28328             return false;
28329         }
28330         // if there is a connection and readyState is not 0 or 4
28331         var state = request.xhr.readyState;
28332         return !(state === 0 || state == 4);
28333     },
28334
28335     /**
28336      * Aborts an active request.
28337      * @param {Object} [request] Defaults to the last request
28338      */
28339     abort : function(request) {
28340         var me = this;
28341         
28342         if (!request) {
28343             request = me.getLatest();
28344         }
28345
28346         if (request && me.isLoading(request)) {
28347             /*
28348              * Clear out the onreadystatechange here, this allows us
28349              * greater control, the browser may/may not fire the function
28350              * depending on a series of conditions.
28351              */
28352             request.xhr.onreadystatechange = null;
28353             request.xhr.abort();
28354             me.clearTimeout(request);
28355             if (!request.timedout) {
28356                 request.aborted = true;
28357             }
28358             me.onComplete(request);
28359             me.cleanup(request);
28360         }
28361     },
28362     
28363     /**
28364      * Aborts all active requests
28365      */
28366     abortAll: function(){
28367         var requests = this.requests,
28368             id;
28369         
28370         for (id in requests) {
28371             if (requests.hasOwnProperty(id)) {
28372                 this.abort(requests[id]);
28373             }
28374         }
28375     },
28376     
28377     /**
28378      * Gets the most recent request
28379      * @private
28380      * @return {Object} The request. Null if there is no recent request
28381      */
28382     getLatest: function(){
28383         var id = this.latestId,
28384             request;
28385             
28386         if (id) {
28387             request = this.requests[id];
28388         }
28389         return request || null;
28390     },
28391
28392     /**
28393      * Fires when the state of the xhr changes
28394      * @private
28395      * @param {Object} request The request
28396      */
28397     onStateChange : function(request) {
28398         if (request.xhr.readyState == 4) {
28399             this.clearTimeout(request);
28400             this.onComplete(request);
28401             this.cleanup(request);
28402         }
28403     },
28404
28405     /**
28406      * Clears the timeout on the request
28407      * @private
28408      * @param {Object} The request
28409      */
28410     clearTimeout: function(request){
28411         clearTimeout(request.timeout);
28412         delete request.timeout;
28413     },
28414
28415     /**
28416      * Cleans up any left over information from the request
28417      * @private
28418      * @param {Object} The request
28419      */
28420     cleanup: function(request){
28421         request.xhr = null;
28422         delete request.xhr;
28423     },
28424
28425     /**
28426      * To be called when the request has come back from the server
28427      * @private
28428      * @param {Object} request
28429      * @return {Object} The response
28430      */
28431     onComplete : function(request) {
28432         var me = this,
28433             options = request.options,
28434             result,
28435             success,
28436             response;
28437
28438         try {
28439             result = me.parseStatus(request.xhr.status);
28440         } catch (e) {
28441             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
28442             result = {
28443                 success : false,
28444                 isException : false
28445             };
28446         }
28447         success = result.success;
28448
28449         if (success) {
28450             response = me.createResponse(request);
28451             me.fireEvent('requestcomplete', me, response, options);
28452             Ext.callback(options.success, options.scope, [response, options]);
28453         } else {
28454             if (result.isException || request.aborted || request.timedout) {
28455                 response = me.createException(request);
28456             } else {
28457                 response = me.createResponse(request);
28458             }
28459             me.fireEvent('requestexception', me, response, options);
28460             Ext.callback(options.failure, options.scope, [response, options]);
28461         }
28462         Ext.callback(options.callback, options.scope, [options, success, response]);
28463         delete me.requests[request.id];
28464         return response;
28465     },
28466
28467     /**
28468      * Checks if the response status was successful
28469      * @param {Number} status The status code
28470      * @return {Object} An object containing success/status state
28471      */
28472     parseStatus: function(status) {
28473         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
28474         status = status == 1223 ? 204 : status;
28475
28476         var success = (status >= 200 && status < 300) || status == 304,
28477             isException = false;
28478
28479         if (!success) {
28480             switch (status) {
28481                 case 12002:
28482                 case 12029:
28483                 case 12030:
28484                 case 12031:
28485                 case 12152:
28486                 case 13030:
28487                     isException = true;
28488                     break;
28489             }
28490         }
28491         return {
28492             success: success,
28493             isException: isException
28494         };
28495     },
28496
28497     /**
28498      * Creates the response object
28499      * @private
28500      * @param {Object} request
28501      */
28502     createResponse : function(request) {
28503         var xhr = request.xhr,
28504             headers = {},
28505             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
28506             count = lines.length,
28507             line, index, key, value, response;
28508
28509         while (count--) {
28510             line = lines[count];
28511             index = line.indexOf(':');
28512             if(index >= 0) {
28513                 key = line.substr(0, index).toLowerCase();
28514                 if (line.charAt(index + 1) == ' ') {
28515                     ++index;
28516                 }
28517                 headers[key] = line.substr(index + 1);
28518             }
28519         }
28520
28521         request.xhr = null;
28522         delete request.xhr;
28523
28524         response = {
28525             request: request,
28526             requestId : request.id,
28527             status : xhr.status,
28528             statusText : xhr.statusText,
28529             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
28530             getAllResponseHeaders : function(){ return headers; },
28531             responseText : xhr.responseText,
28532             responseXML : xhr.responseXML
28533         };
28534
28535         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
28536         // functions created with getResponseHeader/getAllResponseHeaders
28537         xhr = null;
28538         return response;
28539     },
28540
28541     /**
28542      * Creates the exception object
28543      * @private
28544      * @param {Object} request
28545      */
28546     createException : function(request) {
28547         return {
28548             request : request,
28549             requestId : request.id,
28550             status : request.aborted ? -1 : 0,
28551             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
28552             aborted: request.aborted,
28553             timedout: request.timedout
28554         };
28555     }
28556 });
28557
28558 /**
28559  * @class Ext.Ajax
28560  * @singleton
28561  * @markdown
28562  * @extends Ext.data.Connection
28563
28564 A singleton instance of an {@link Ext.data.Connection}. This class
28565 is used to communicate with your server side code. It can be used as follows:
28566
28567     Ext.Ajax.request({
28568         url: 'page.php',
28569         params: {
28570             id: 1
28571         },
28572         success: function(response){
28573             var text = response.responseText;
28574             // process server response here
28575         }
28576     });
28577
28578 Default options for all requests can be set by changing a property on the Ext.Ajax class:
28579
28580     Ext.Ajax.timeout = 60000; // 60 seconds
28581
28582 Any options specified in the request method for the Ajax request will override any
28583 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
28584 request will be 60 seconds.
28585
28586     Ext.Ajax.timeout = 120000; // 120 seconds
28587     Ext.Ajax.request({
28588         url: 'page.aspx',
28589         timeout: 60000
28590     });
28591
28592 In general, this class will be used for all Ajax requests in your application.
28593 The main reason for creating a separate {@link Ext.data.Connection} is for a
28594 series of requests that share common settings that are different to all other
28595 requests in the application.
28596
28597  */
28598 Ext.define('Ext.Ajax', {
28599     extend: 'Ext.data.Connection',
28600     singleton: true,
28601
28602     /**
28603      * @cfg {String} url @hide
28604      */
28605     /**
28606      * @cfg {Object} extraParams @hide
28607      */
28608     /**
28609      * @cfg {Object} defaultHeaders @hide
28610      */
28611     /**
28612      * @cfg {String} method (Optional) @hide
28613      */
28614     /**
28615      * @cfg {Number} timeout (Optional) @hide
28616      */
28617     /**
28618      * @cfg {Boolean} autoAbort (Optional) @hide
28619      */
28620
28621     /**
28622      * @cfg {Boolean} disableCaching (Optional) @hide
28623      */
28624
28625     /**
28626      * @property {Boolean} disableCaching
28627      * True to add a unique cache-buster param to GET requests. Defaults to true.
28628      */
28629     /**
28630      * @property {String} url
28631      * The default URL to be used for requests to the server.
28632      * If the server receives all requests through one URL, setting this once is easier than
28633      * entering it on every request.
28634      */
28635     /**
28636      * @property {Object} extraParams
28637      * An object containing properties which are used as extra parameters to each request made
28638      * by this object. Session information and other data that you need
28639      * to pass with each request are commonly put here.
28640      */
28641     /**
28642      * @property {Object} defaultHeaders
28643      * An object containing request headers which are added to each request made by this object.
28644      */
28645     /**
28646      * @property {String} method
28647      * The default HTTP method to be used for requests. Note that this is case-sensitive and
28648      * should be all caps (if not set but params are present will use
28649      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
28650      */
28651     /**
28652      * @property {Number} timeout
28653      * The timeout in milliseconds to be used for requests. Defaults to 30000.
28654      */
28655
28656     /**
28657      * @property {Boolean} autoAbort
28658      * Whether a new request should abort any pending requests.
28659      */
28660     autoAbort : false
28661 });
28662 /**
28663  * A class used to load remote content to an Element. Sample usage:
28664  *
28665  *     Ext.get('el').load({
28666  *         url: 'myPage.php',
28667  *         scripts: true,
28668  *         params: {
28669  *             id: 1
28670  *         }
28671  *     });
28672  *
28673  * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
28674  * will be used.
28675  */
28676 Ext.define('Ext.ElementLoader', {
28677
28678     /* Begin Definitions */
28679
28680     mixins: {
28681         observable: 'Ext.util.Observable'
28682     },
28683
28684     uses: [
28685         'Ext.data.Connection',
28686         'Ext.Ajax'
28687     ],
28688
28689     statics: {
28690         Renderer: {
28691             Html: function(loader, response, active){
28692                 loader.getTarget().update(response.responseText, active.scripts === true);
28693                 return true;
28694             }
28695         }
28696     },
28697
28698     /* End Definitions */
28699
28700     /**
28701      * @cfg {String} url
28702      * The url to retrieve the content from.
28703      */
28704     url: null,
28705
28706     /**
28707      * @cfg {Object} params
28708      * Any params to be attached to the Ajax request. These parameters will
28709      * be overridden by any params in the load options.
28710      */
28711     params: null,
28712
28713     /**
28714      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
28715      * will not be overridden by any params in the load options.
28716      */
28717     baseParams: null,
28718
28719     /**
28720      * @cfg {Boolean/Object} autoLoad
28721      * True to have the loader make a request as soon as it is created.
28722      * This argument can also be a set of options that will be passed to {@link #load} is called.
28723      */
28724     autoLoad: false,
28725
28726     /**
28727      * @cfg {HTMLElement/Ext.Element/String} target
28728      * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
28729      */
28730     target: null,
28731
28732     /**
28733      * @cfg {Boolean/String} loadMask
28734      * True or a string to show when the element is loading.
28735      */
28736     loadMask: false,
28737
28738     /**
28739      * @cfg {Object} ajaxOptions
28740      * Any additional options to be passed to the request, for example timeout or headers.
28741      */
28742     ajaxOptions: null,
28743
28744     /**
28745      * @cfg {Boolean} scripts
28746      * True to parse any inline script tags in the response.
28747      */
28748     scripts: false,
28749
28750     /**
28751      * @cfg {Function} success
28752      * A function to be called when a load request is successful.
28753      * Will be called with the following config parameters:
28754      *
28755      * - this - The ElementLoader instance.
28756      * - response - The response object.
28757      * - options - Ajax options.
28758      */
28759
28760     /**
28761      * @cfg {Function} failure A function to be called when a load request fails.
28762      * Will be called with the following config parameters:
28763      *
28764      * - this - The ElementLoader instance.
28765      * - response - The response object.
28766      * - options - Ajax options.
28767      */
28768
28769     /**
28770      * @cfg {Function} callback A function to be called when a load request finishes.
28771      * Will be called with the following config parameters:
28772      *
28773      * - this - The ElementLoader instance.
28774      * - success - True if successful request.
28775      * - response - The response object.
28776      * - options - Ajax options.
28777      */
28778
28779     /**
28780      * @cfg {Object} scope
28781      * The scope to execute the {@link #success} and {@link #failure} functions in.
28782      */
28783
28784     /**
28785      * @cfg {Function} renderer
28786      * A custom function to render the content to the element. The passed parameters are:
28787      *
28788      * - The loader
28789      * - The response
28790      * - The active request
28791      */
28792
28793     isLoader: true,
28794
28795     constructor: function(config) {
28796         var me = this,
28797             autoLoad;
28798
28799         config = config || {};
28800         Ext.apply(me, config);
28801         me.setTarget(me.target);
28802         me.addEvents(
28803             /**
28804              * @event beforeload
28805              * Fires before a load request is made to the server.
28806              * Returning false from an event listener can prevent the load
28807              * from occurring.
28808              * @param {Ext.ElementLoader} this
28809              * @param {Object} options The options passed to the request
28810              */
28811             'beforeload',
28812
28813             /**
28814              * @event exception
28815              * Fires after an unsuccessful load.
28816              * @param {Ext.ElementLoader} this
28817              * @param {Object} response The response from the server
28818              * @param {Object} options The options passed to the request
28819              */
28820             'exception',
28821
28822             /**
28823              * @event load
28824              * Fires after a successful load.
28825              * @param {Ext.ElementLoader} this
28826              * @param {Object} response The response from the server
28827              * @param {Object} options The options passed to the request
28828              */
28829             'load'
28830         );
28831
28832         // don't pass config because we have already applied it.
28833         me.mixins.observable.constructor.call(me);
28834
28835         if (me.autoLoad) {
28836             autoLoad = me.autoLoad;
28837             if (autoLoad === true) {
28838                 autoLoad = {};
28839             }
28840             me.load(autoLoad);
28841         }
28842     },
28843
28844     /**
28845      * Sets an {@link Ext.Element} as the target of this loader.
28846      * Note that if the target is changed, any active requests will be aborted.
28847      * @param {String/HTMLElement/Ext.Element} target The element or its ID.
28848      */
28849     setTarget: function(target){
28850         var me = this;
28851         target = Ext.get(target);
28852         if (me.target && me.target != target) {
28853             me.abort();
28854         }
28855         me.target = target;
28856     },
28857
28858     /**
28859      * Returns the target of this loader.
28860      * @return {Ext.Component} The target or null if none exists.
28861      */
28862     getTarget: function(){
28863         return this.target || null;
28864     },
28865
28866     /**
28867      * Aborts the active load request
28868      */
28869     abort: function(){
28870         var active = this.active;
28871         if (active !== undefined) {
28872             Ext.Ajax.abort(active.request);
28873             if (active.mask) {
28874                 this.removeMask();
28875             }
28876             delete this.active;
28877         }
28878     },
28879
28880     /**
28881      * Removes the mask on the target
28882      * @private
28883      */
28884     removeMask: function(){
28885         this.target.unmask();
28886     },
28887
28888     /**
28889      * Adds the mask on the target
28890      * @private
28891      * @param {Boolean/Object} mask The mask configuration
28892      */
28893     addMask: function(mask){
28894         this.target.mask(mask === true ? null : mask);
28895     },
28896
28897     /**
28898      * Loads new data from the server.
28899      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
28900      * the class, with the exception of the target option. Note that any options passed to the method will override any
28901      * class defaults.
28902      */
28903     load: function(options) {
28904         if (!this.target) {
28905             Ext.Error.raise('A valid target is required when loading content');
28906         }
28907
28908         options = Ext.apply({}, options);
28909
28910         var me = this,
28911             target = me.target,
28912             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
28913             params = Ext.apply({}, options.params),
28914             ajaxOptions = Ext.apply({}, options.ajaxOptions),
28915             callback = options.callback || me.callback,
28916             scope = options.scope || me.scope || me,
28917             request;
28918
28919         Ext.applyIf(ajaxOptions, me.ajaxOptions);
28920         Ext.applyIf(options, ajaxOptions);
28921
28922         Ext.applyIf(params, me.params);
28923         Ext.apply(params, me.baseParams);
28924
28925         Ext.applyIf(options, {
28926             url: me.url
28927         });
28928
28929         if (!options.url) {
28930             Ext.Error.raise('You must specify the URL from which content should be loaded');
28931         }
28932
28933         Ext.apply(options, {
28934             scope: me,
28935             params: params,
28936             callback: me.onComplete
28937         });
28938
28939         if (me.fireEvent('beforeload', me, options) === false) {
28940             return;
28941         }
28942
28943         if (mask) {
28944             me.addMask(mask);
28945         }
28946
28947         request = Ext.Ajax.request(options);
28948         me.active = {
28949             request: request,
28950             options: options,
28951             mask: mask,
28952             scope: scope,
28953             callback: callback,
28954             success: options.success || me.success,
28955             failure: options.failure || me.failure,
28956             renderer: options.renderer || me.renderer,
28957             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
28958         };
28959         me.setOptions(me.active, options);
28960     },
28961
28962     /**
28963      * Sets any additional options on the active request
28964      * @private
28965      * @param {Object} active The active request
28966      * @param {Object} options The initial options
28967      */
28968     setOptions: Ext.emptyFn,
28969
28970     /**
28971      * Parses the response after the request completes
28972      * @private
28973      * @param {Object} options Ajax options
28974      * @param {Boolean} success Success status of the request
28975      * @param {Object} response The response object
28976      */
28977     onComplete: function(options, success, response) {
28978         var me = this,
28979             active = me.active,
28980             scope = active.scope,
28981             renderer = me.getRenderer(active.renderer);
28982
28983
28984         if (success) {
28985             success = renderer.call(me, me, response, active);
28986         }
28987
28988         if (success) {
28989             Ext.callback(active.success, scope, [me, response, options]);
28990             me.fireEvent('load', me, response, options);
28991         } else {
28992             Ext.callback(active.failure, scope, [me, response, options]);
28993             me.fireEvent('exception', me, response, options);
28994         }
28995         Ext.callback(active.callback, scope, [me, success, response, options]);
28996
28997         if (active.mask) {
28998             me.removeMask();
28999         }
29000
29001         delete me.active;
29002     },
29003
29004     /**
29005      * Gets the renderer to use
29006      * @private
29007      * @param {String/Function} renderer The renderer to use
29008      * @return {Function} A rendering function to use.
29009      */
29010     getRenderer: function(renderer){
29011         if (Ext.isFunction(renderer)) {
29012             return renderer;
29013         }
29014         return this.statics().Renderer.Html;
29015     },
29016
29017     /**
29018      * Automatically refreshes the content over a specified period.
29019      * @param {Number} interval The interval to refresh in ms.
29020      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
29021      */
29022     startAutoRefresh: function(interval, options){
29023         var me = this;
29024         me.stopAutoRefresh();
29025         me.autoRefresh = setInterval(function(){
29026             me.load(options);
29027         }, interval);
29028     },
29029
29030     /**
29031      * Clears any auto refresh. See {@link #startAutoRefresh}.
29032      */
29033     stopAutoRefresh: function(){
29034         clearInterval(this.autoRefresh);
29035         delete this.autoRefresh;
29036     },
29037
29038     /**
29039      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
29040      * @return {Boolean} True if the loader is automatically refreshing
29041      */
29042     isAutoRefreshing: function(){
29043         return Ext.isDefined(this.autoRefresh);
29044     },
29045
29046     /**
29047      * Destroys the loader. Any active requests will be aborted.
29048      */
29049     destroy: function(){
29050         var me = this;
29051         me.stopAutoRefresh();
29052         delete me.target;
29053         me.abort();
29054         me.clearListeners();
29055     }
29056 });
29057
29058 /**
29059  * @class Ext.ComponentLoader
29060  * @extends Ext.ElementLoader
29061  *
29062  * This class is used to load content via Ajax into a {@link Ext.Component}. In general
29063  * this class will not be instanced directly, rather a loader configuration will be passed to the
29064  * constructor of the {@link Ext.Component}.
29065  *
29066  * ## HTML Renderer
29067  * By default, the content loaded will be processed as raw html. The response text
29068  * from the request is taken and added to the component. This can be used in
29069  * conjunction with the {@link #scripts} option to execute any inline scripts in
29070  * the resulting content. Using this renderer has the same effect as passing the
29071  * {@link Ext.Component#html} configuration option.
29072  *
29073  * ## Data Renderer
29074  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
29075  * The content received from the response is passed to the {@link Ext.Component#update} method.
29076  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
29077  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
29078  * configuration in conjunction with a {@link Ext.Component#tpl}.
29079  *
29080  * ## Component Renderer
29081  * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
29082  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
29083  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
29084  * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
29085  * the {@link Ext.container.Container#items} configuration on a Container.
29086  *
29087  * ## Custom Renderer
29088  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
29089  *
29090  * ## Example Usage
29091  *     new Ext.Component({
29092  *         tpl: '{firstName} - {lastName}',
29093  *         loader: {
29094  *             url: 'myPage.php',
29095  *             renderer: 'data',
29096  *             params: {
29097  *                 userId: 1
29098  *             }
29099  *         }
29100  *     });
29101  */
29102 Ext.define('Ext.ComponentLoader', {
29103
29104     /* Begin Definitions */
29105
29106     extend: 'Ext.ElementLoader',
29107
29108     statics: {
29109         Renderer: {
29110             Data: function(loader, response, active){
29111                 var success = true;
29112                 try {
29113                     loader.getTarget().update(Ext.decode(response.responseText));
29114                 } catch (e) {
29115                     success = false;
29116                 }
29117                 return success;
29118             },
29119
29120             Component: function(loader, response, active){
29121                 var success = true,
29122                     target = loader.getTarget(),
29123                     items = [];
29124
29125                 if (!target.isContainer) {
29126                     Ext.Error.raise({
29127                         target: target,
29128                         msg: 'Components can only be loaded into a container'
29129                     });
29130                 }
29131
29132                 try {
29133                     items = Ext.decode(response.responseText);
29134                 } catch (e) {
29135                     success = false;
29136                 }
29137
29138                 if (success) {
29139                     if (active.removeAll) {
29140                         target.removeAll();
29141                     }
29142                     target.add(items);
29143                 }
29144                 return success;
29145             }
29146         }
29147     },
29148
29149     /* End Definitions */
29150
29151     /**
29152      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
29153      * If a string is passed it will be looked up via the id.
29154      */
29155     target: null,
29156
29157     /**
29158      * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
29159      */
29160     loadMask: false,
29161
29162     /**
29163      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
29164      * {@link #renderer}.
29165      */
29166
29167     /**
29168      * @cfg {String/Function} renderer
29169
29170 The type of content that is to be loaded into, which can be one of 3 types:
29171
29172 + **html** : Loads raw html content, see {@link Ext.Component#html}
29173 + **data** : Loads raw html content, see {@link Ext.Component#data}
29174 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
29175
29176 Alternatively, you can pass a function which is called with the following parameters.
29177
29178 + loader - Loader instance
29179 + response - The server response
29180 + active - The active request
29181
29182 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
29183
29184     new Ext.Component({
29185         loader: {
29186             url: 'myPage.php',
29187             renderer: function(loader, response, active) {
29188                 var text = response.responseText;
29189                 loader.getTarget().update('The response is ' + text);
29190                 return true;
29191             }
29192         }
29193     });
29194      */
29195     renderer: 'html',
29196
29197     /**
29198      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
29199      * any active requests will be aborted.
29200      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
29201      * it will be looked up via its id.
29202      */
29203     setTarget: function(target){
29204         var me = this;
29205
29206         if (Ext.isString(target)) {
29207             target = Ext.getCmp(target);
29208         }
29209
29210         if (me.target && me.target != target) {
29211             me.abort();
29212         }
29213         me.target = target;
29214     },
29215
29216     // inherit docs
29217     removeMask: function(){
29218         this.target.setLoading(false);
29219     },
29220
29221     /**
29222      * Add the mask on the target
29223      * @private
29224      * @param {Boolean/Object} mask The mask configuration
29225      */
29226     addMask: function(mask){
29227         this.target.setLoading(mask);
29228     },
29229
29230     /**
29231      * Get the target of this loader.
29232      * @return {Ext.Component} target The target, null if none exists.
29233      */
29234
29235     setOptions: function(active, options){
29236         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
29237     },
29238
29239     /**
29240      * Gets the renderer to use
29241      * @private
29242      * @param {String/Function} renderer The renderer to use
29243      * @return {Function} A rendering function to use.
29244      */
29245     getRenderer: function(renderer){
29246         if (Ext.isFunction(renderer)) {
29247             return renderer;
29248         }
29249
29250         var renderers = this.statics().Renderer;
29251         switch (renderer) {
29252             case 'component':
29253                 return renderers.Component;
29254             case 'data':
29255                 return renderers.Data;
29256             default:
29257                 return Ext.ElementLoader.Renderer.Html;
29258         }
29259     }
29260 });
29261
29262 /**
29263  * @author Ed Spencer
29264  *
29265  * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
29266  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
29267  * express like this:
29268  *
29269  *     Ext.define('User', {
29270  *         extend: 'Ext.data.Model',
29271  *         fields: ['id', 'name', 'email'],
29272  *
29273  *         hasMany: {model: 'Order', name: 'orders'}
29274  *     });
29275  *
29276  *     Ext.define('Order', {
29277  *         extend: 'Ext.data.Model',
29278  *         fields: ['id', 'user_id', 'status', 'price'],
29279  *
29280  *         belongsTo: 'User'
29281  *     });
29282  *
29283  * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
29284  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
29285  * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
29286  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
29287  *
29288  * **Further Reading**
29289  *
29290  *   - {@link Ext.data.HasManyAssociation hasMany associations}
29291  *   - {@link Ext.data.BelongsToAssociation belongsTo associations}
29292  *   - {@link Ext.data.Model using Models}
29293  *
29294  * # Self association models
29295  *
29296  * We can also have models that create parent/child associations between the same type. Below is an example, where
29297  * groups can be nested inside other groups:
29298  *
29299  *     // Server Data
29300  *     {
29301  *         "groups": {
29302  *             "id": 10,
29303  *             "parent_id": 100,
29304  *             "name": "Main Group",
29305  *             "parent_group": {
29306  *                 "id": 100,
29307  *                 "parent_id": null,
29308  *                 "name": "Parent Group"
29309  *             },
29310  *             "child_groups": [{
29311  *                 "id": 2,
29312  *                 "parent_id": 10,
29313  *                 "name": "Child Group 1"
29314  *             },{
29315  *                 "id": 3,
29316  *                 "parent_id": 10,
29317  *                 "name": "Child Group 2"
29318  *             },{
29319  *                 "id": 4,
29320  *                 "parent_id": 10,
29321  *                 "name": "Child Group 3"
29322  *             }]
29323  *         }
29324  *     }
29325  *
29326  *     // Client code
29327  *     Ext.define('Group', {
29328  *         extend: 'Ext.data.Model',
29329  *         fields: ['id', 'parent_id', 'name'],
29330  *         proxy: {
29331  *             type: 'ajax',
29332  *             url: 'data.json',
29333  *             reader: {
29334  *                 type: 'json',
29335  *                 root: 'groups'
29336  *             }
29337  *         },
29338  *         associations: [{
29339  *             type: 'hasMany',
29340  *             model: 'Group',
29341  *             primaryKey: 'id',
29342  *             foreignKey: 'parent_id',
29343  *             autoLoad: true,
29344  *             associationKey: 'child_groups' // read child data from child_groups
29345  *         }, {
29346  *             type: 'belongsTo',
29347  *             model: 'Group',
29348  *             primaryKey: 'id',
29349  *             foreignKey: 'parent_id',
29350  *             associationKey: 'parent_group' // read parent data from parent_group
29351  *         }]
29352  *     });
29353  *
29354  *     Ext.onReady(function(){
29355  *
29356  *         Group.load(10, {
29357  *             success: function(group){
29358  *                 console.log(group.getGroup().get('name'));
29359  *
29360  *                 group.groups().each(function(rec){
29361  *                     console.log(rec.get('name'));
29362  *                 });
29363  *             }
29364  *         });
29365  *
29366  *     });
29367  *
29368  */
29369 Ext.define('Ext.data.Association', {
29370     /**
29371      * @cfg {String} ownerModel (required)
29372      * The string name of the model that owns the association.
29373      */
29374
29375     /**
29376      * @cfg {String} associatedModel (required)
29377      * The string name of the model that is being associated with.
29378      */
29379
29380     /**
29381      * @cfg {String} primaryKey
29382      * The name of the primary key on the associated model. In general this will be the
29383      * {@link Ext.data.Model#idProperty} of the Model.
29384      */
29385     primaryKey: 'id',
29386
29387     /**
29388      * @cfg {Ext.data.reader.Reader} reader
29389      * A special reader to read associated data
29390      */
29391     
29392     /**
29393      * @cfg {String} associationKey
29394      * The name of the property in the data to read the association from. Defaults to the name of the associated model.
29395      */
29396
29397     defaultReaderType: 'json',
29398
29399     statics: {
29400         create: function(association){
29401             if (!association.isAssociation) {
29402                 if (Ext.isString(association)) {
29403                     association = {
29404                         type: association
29405                     };
29406                 }
29407
29408                 switch (association.type) {
29409                     case 'belongsTo':
29410                         return Ext.create('Ext.data.BelongsToAssociation', association);
29411                     case 'hasMany':
29412                         return Ext.create('Ext.data.HasManyAssociation', association);
29413                     //TODO Add this back when it's fixed
29414 //                    case 'polymorphic':
29415 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
29416                     default:
29417                         Ext.Error.raise('Unknown Association type: "' + association.type + '"');
29418                 }
29419             }
29420             return association;
29421         }
29422     },
29423
29424     /**
29425      * Creates the Association object.
29426      * @param {Object} [config] Config object.
29427      */
29428     constructor: function(config) {
29429         Ext.apply(this, config);
29430
29431         var types           = Ext.ModelManager.types,
29432             ownerName       = config.ownerModel,
29433             associatedName  = config.associatedModel,
29434             ownerModel      = types[ownerName],
29435             associatedModel = types[associatedName],
29436             ownerProto;
29437
29438         if (ownerModel === undefined) {
29439             Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
29440         }
29441         if (associatedModel === undefined) {
29442             Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
29443         }
29444
29445         this.ownerModel = ownerModel;
29446         this.associatedModel = associatedModel;
29447
29448         /**
29449          * @property {String} ownerName
29450          * The name of the model that 'owns' the association
29451          */
29452
29453         /**
29454          * @property {String} associatedName
29455          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
29456          * 'Order')
29457          */
29458
29459         Ext.applyIf(this, {
29460             ownerName : ownerName,
29461             associatedName: associatedName
29462         });
29463     },
29464
29465     /**
29466      * Get a specialized reader for reading associated data
29467      * @return {Ext.data.reader.Reader} The reader, null if not supplied
29468      */
29469     getReader: function(){
29470         var me = this,
29471             reader = me.reader,
29472             model = me.associatedModel;
29473
29474         if (reader) {
29475             if (Ext.isString(reader)) {
29476                 reader = {
29477                     type: reader
29478                 };
29479             }
29480             if (reader.isReader) {
29481                 reader.setModel(model);
29482             } else {
29483                 Ext.applyIf(reader, {
29484                     model: model,
29485                     type : me.defaultReaderType
29486                 });
29487             }
29488             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
29489         }
29490         return me.reader || null;
29491     }
29492 });
29493
29494 /**
29495  * @author Ed Spencer
29496  * @class Ext.ModelManager
29497  * @extends Ext.AbstractManager
29498
29499 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
29500
29501 __Creating Model Instances__
29502
29503 Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
29504 the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
29505 this by using the Model type directly. The following 3 snippets are equivalent:
29506
29507     Ext.define('User', {
29508         extend: 'Ext.data.Model',
29509         fields: ['first', 'last']
29510     });
29511
29512     // method 1, create using Ext.create (recommended)
29513     Ext.create('User', {
29514         first: 'Ed',
29515         last: 'Spencer'
29516     });
29517
29518     // method 2, create through the manager (deprecated)
29519     Ext.ModelManager.create({
29520         first: 'Ed',
29521         last: 'Spencer'
29522     }, 'User');
29523
29524     // method 3, create on the type directly
29525     new User({
29526         first: 'Ed',
29527         last: 'Spencer'
29528     });
29529
29530 __Accessing Model Types__
29531
29532 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
29533 are normal classes, you can access the type directly. The following snippets are equivalent:
29534
29535     Ext.define('User', {
29536         extend: 'Ext.data.Model',
29537         fields: ['first', 'last']
29538     });
29539
29540     // method 1, access model type through the manager
29541     var UserType = Ext.ModelManager.getModel('User');
29542
29543     // method 2, reference the type directly
29544     var UserType = User;
29545
29546  * @markdown
29547  * @singleton
29548  */
29549 Ext.define('Ext.ModelManager', {
29550     extend: 'Ext.AbstractManager',
29551     alternateClassName: 'Ext.ModelMgr',
29552     requires: ['Ext.data.Association'],
29553
29554     singleton: true,
29555
29556     typeName: 'mtype',
29557
29558     /**
29559      * Private stack of associations that must be created once their associated model has been defined
29560      * @property {Ext.data.Association[]} associationStack
29561      */
29562     associationStack: [],
29563
29564     /**
29565      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
29566      * immediately, as are any addition plugins defined in the model config.
29567      * @private
29568      */
29569     registerType: function(name, config) {
29570         var proto = config.prototype,
29571             model;
29572         if (proto && proto.isModel) {
29573             // registering an already defined model
29574             model = config;
29575         } else {
29576             // passing in a configuration
29577             if (!config.extend) {
29578                 config.extend = 'Ext.data.Model';
29579             }
29580             model = Ext.define(name, config);
29581         }
29582         this.types[name] = model;
29583         return model;
29584     },
29585
29586     /**
29587      * @private
29588      * Private callback called whenever a model has just been defined. This sets up any associations
29589      * that were waiting for the given model to be defined
29590      * @param {Function} model The model that was just created
29591      */
29592     onModelDefined: function(model) {
29593         var stack  = this.associationStack,
29594             length = stack.length,
29595             create = [],
29596             association, i, created;
29597
29598         for (i = 0; i < length; i++) {
29599             association = stack[i];
29600
29601             if (association.associatedModel == model.modelName) {
29602                 create.push(association);
29603             }
29604         }
29605
29606         for (i = 0, length = create.length; i < length; i++) {
29607             created = create[i];
29608             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
29609             Ext.Array.remove(stack, created);
29610         }
29611     },
29612
29613     /**
29614      * Registers an association where one of the models defined doesn't exist yet.
29615      * The ModelManager will check when new models are registered if it can link them
29616      * together
29617      * @private
29618      * @param {Ext.data.Association} association The association
29619      */
29620     registerDeferredAssociation: function(association){
29621         this.associationStack.push(association);
29622     },
29623
29624     /**
29625      * Returns the {@link Ext.data.Model} for a given model name
29626      * @param {String/Object} id The id of the model or the model instance.
29627      * @return {Ext.data.Model} a model class.
29628      */
29629     getModel: function(id) {
29630         var model = id;
29631         if (typeof model == 'string') {
29632             model = this.types[model];
29633         }
29634         return model;
29635     },
29636
29637     /**
29638      * Creates a new instance of a Model using the given data.
29639      *
29640      * This method is deprecated.  Use {@link Ext#create Ext.create} instead.  For example:
29641      *
29642      *     Ext.create('User', {
29643      *         first: 'Ed',
29644      *         last: 'Spencer'
29645      *     });
29646      *
29647      * @param {Object} data Data to initialize the Model's fields with
29648      * @param {String} name The name of the model to create
29649      * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
29650      */
29651     create: function(config, name, id) {
29652         var con = typeof name == 'function' ? name : this.types[name || config.name];
29653
29654         return new con(config, id);
29655     }
29656 }, function() {
29657
29658     /**
29659      * Old way for creating Model classes.  Instead use:
29660      *
29661      *     Ext.define("MyModel", {
29662      *         extend: "Ext.data.Model",
29663      *         fields: []
29664      *     });
29665      *
29666      * @param {String} name Name of the Model class.
29667      * @param {Object} config A configuration object for the Model you wish to create.
29668      * @return {Ext.data.Model} The newly registered Model
29669      * @member Ext
29670      * @deprecated 4.0.0 Use {@link Ext#define} instead.
29671      */
29672     Ext.regModel = function() {
29673         if (Ext.isDefined(Ext.global.console)) {
29674             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: []});.');
29675         }
29676         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
29677     };
29678 });
29679
29680 /**
29681  * @singleton
29682  *
29683  * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
29684  *
29685  * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
29686  *
29687  *     {
29688  *         ptype: 'gridviewdragdrop',
29689  *         dragText: 'Drag and drop to reorganize'
29690  *     }
29691  *
29692  * Or just use the ptype on its own:
29693  *
29694  *     'gridviewdragdrop'
29695  *
29696  * Alternatively you can instantiate the plugin with Ext.create:
29697  *
29698  *     Ext.create('Ext.view.plugin.AutoComplete', {
29699  *         ptype: 'gridviewdragdrop',
29700  *         dragText: 'Drag and drop to reorganize'
29701  *     })
29702  */
29703 Ext.define('Ext.PluginManager', {
29704     extend: 'Ext.AbstractManager',
29705     alternateClassName: 'Ext.PluginMgr',
29706     singleton: true,
29707     typeName: 'ptype',
29708
29709     /**
29710      * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
29711      * instantiate.
29712      * @param {Object} config A configuration object for the Plugin you wish to create.
29713      * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
29714      * contain a `ptype`. (Optional if the config contains a `ptype`).
29715      * @return {Ext.Component} The newly instantiated Plugin.
29716      */
29717     //create: function(plugin, defaultType) {
29718     //    if (plugin instanceof this) {
29719     //        return plugin;
29720     //    } else {
29721     //        var type, config = {};
29722     //
29723     //        if (Ext.isString(plugin)) {
29724     //            type = plugin;
29725     //        }
29726     //        else {
29727     //            type = plugin[this.typeName] || defaultType;
29728     //            config = plugin;
29729     //        }
29730     //
29731     //        return Ext.createByAlias('plugin.' + type, config);
29732     //    }
29733     //},
29734
29735     create : function(config, defaultType){
29736         if (config.init) {
29737             return config;
29738         } else {
29739             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
29740         }
29741
29742         // Prior system supported Singleton plugins.
29743         //var PluginCls = this.types[config.ptype || defaultType];
29744         //if (PluginCls.init) {
29745         //    return PluginCls;
29746         //} else {
29747         //    return new PluginCls(config);
29748         //}
29749     },
29750
29751     /**
29752      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
29753      * @param {String} type The type to search for
29754      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
29755      * truthy
29756      * @return {Ext.AbstractPlugin[]} All matching plugins
29757      */
29758     findByType: function(type, defaultsOnly) {
29759         var matches = [],
29760             types   = this.types;
29761
29762         for (var name in types) {
29763             if (!types.hasOwnProperty(name)) {
29764                 continue;
29765             }
29766             var item = types[name];
29767
29768             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
29769                 matches.push(item);
29770             }
29771         }
29772
29773         return matches;
29774     }
29775 }, function() {
29776     /**
29777      * Shorthand for {@link Ext.PluginManager#registerType}
29778      * @param {String} ptype The ptype mnemonic string by which the Plugin class
29779      * may be looked up.
29780      * @param {Function} cls The new Plugin class.
29781      * @member Ext
29782      * @method preg
29783      */
29784     Ext.preg = function() {
29785         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
29786     };
29787 });
29788
29789 /**
29790  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
29791  *
29792  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
29793  * arguments:
29794  *
29795  * # Single argument: String/Array
29796  *
29797  * The single argument may be either a String or an Array:
29798  *
29799  * - String:
29800  *
29801  *       var t = new Ext.Template("<div>Hello {0}.</div>");
29802  *       t.{@link #append}('some-element', ['foo']);
29803  *
29804  * - Array:
29805  *
29806  *   An Array will be combined with `join('')`.
29807  *
29808  *       var t = new Ext.Template([
29809  *           '<div name="{id}">',
29810  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
29811  *           '</div>',
29812  *       ]);
29813  *       t.{@link #compile}();
29814  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
29815  *
29816  * # Multiple arguments: String, Object, Array, ...
29817  *
29818  * Multiple arguments will be combined with `join('')`.
29819  *
29820  *     var t = new Ext.Template(
29821  *         '<div name="{id}">',
29822  *             '<span class="{cls}">{name} {value}</span>',
29823  *         '</div>',
29824  *         // a configuration object:
29825  *         {
29826  *             compiled: true,      // {@link #compile} immediately
29827  *         }
29828  *     );
29829  *
29830  * # Notes
29831  *
29832  * - For a list of available format functions, see {@link Ext.util.Format}.
29833  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
29834  */
29835 Ext.define('Ext.Template', {
29836
29837     /* Begin Definitions */
29838
29839     requires: ['Ext.DomHelper', 'Ext.util.Format'],
29840
29841     inheritableStatics: {
29842         /**
29843          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
29844          * @param {String/HTMLElement} el A DOM element or its id
29845          * @param {Object} config (optional) Config object
29846          * @return {Ext.Template} The created template
29847          * @static
29848          * @inheritable
29849          */
29850         from: function(el, config) {
29851             el = Ext.getDom(el);
29852             return new this(el.value || el.innerHTML, config || '');
29853         }
29854     },
29855
29856     /* End Definitions */
29857
29858     /**
29859      * Creates new template.
29860      * 
29861      * @param {String...} html List of strings to be concatenated into template.
29862      * Alternatively an array of strings can be given, but then no config object may be passed.
29863      * @param {Object} config (optional) Config object
29864      */
29865     constructor: function(html) {
29866         var me = this,
29867             args = arguments,
29868             buffer = [],
29869             i = 0,
29870             length = args.length,
29871             value;
29872
29873         me.initialConfig = {};
29874
29875         if (length > 1) {
29876             for (; i < length; i++) {
29877                 value = args[i];
29878                 if (typeof value == 'object') {
29879                     Ext.apply(me.initialConfig, value);
29880                     Ext.apply(me, value);
29881                 } else {
29882                     buffer.push(value);
29883                 }
29884             }
29885             html = buffer.join('');
29886         } else {
29887             if (Ext.isArray(html)) {
29888                 buffer.push(html.join(''));
29889             } else {
29890                 buffer.push(html);
29891             }
29892         }
29893
29894         // @private
29895         me.html = buffer.join('');
29896
29897         if (me.compiled) {
29898             me.compile();
29899         }
29900     },
29901
29902     isTemplate: true,
29903
29904     /**
29905      * @cfg {Boolean} compiled
29906      * True to immediately compile the template. Defaults to false.
29907      */
29908
29909     /**
29910      * @cfg {Boolean} disableFormats
29911      * True to disable format functions in the template. If the template doesn't contain
29912      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
29913      */
29914     disableFormats: false,
29915
29916     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
29917
29918     /**
29919      * Returns an HTML fragment of this template with the specified values applied.
29920      *
29921      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
29922      *
29923      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
29924      *     tpl.applyTemplate(['John', 25]);
29925      *
29926      * or an object:
29927      *
29928      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
29929      *     tpl.applyTemplate({name: 'John', age: 25});
29930      *
29931      * @return {String} The HTML fragment
29932      */
29933     applyTemplate: function(values) {
29934         var me = this,
29935             useFormat = me.disableFormats !== true,
29936             fm = Ext.util.Format,
29937             tpl = me;
29938
29939         if (me.compiled) {
29940             return me.compiled(values);
29941         }
29942         function fn(m, name, format, args) {
29943             if (format && useFormat) {
29944                 if (args) {
29945                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
29946                 } else {
29947                     args = [values[name]];
29948                 }
29949                 if (format.substr(0, 5) == "this.") {
29950                     return tpl[format.substr(5)].apply(tpl, args);
29951                 }
29952                 else {
29953                     return fm[format].apply(fm, args);
29954                 }
29955             }
29956             else {
29957                 return values[name] !== undefined ? values[name] : "";
29958             }
29959         }
29960         return me.html.replace(me.re, fn);
29961     },
29962
29963     /**
29964      * Sets the HTML used as the template and optionally compiles it.
29965      * @param {String} html
29966      * @param {Boolean} compile (optional) True to compile the template.
29967      * @return {Ext.Template} this
29968      */
29969     set: function(html, compile) {
29970         var me = this;
29971         me.html = html;
29972         me.compiled = null;
29973         return compile ? me.compile() : me;
29974     },
29975
29976     compileARe: /\\/g,
29977     compileBRe: /(\r\n|\n)/g,
29978     compileCRe: /'/g,
29979
29980     /**
29981      * Compiles the template into an internal function, eliminating the RegEx overhead.
29982      * @return {Ext.Template} this
29983      */
29984     compile: function() {
29985         var me = this,
29986             fm = Ext.util.Format,
29987             useFormat = me.disableFormats !== true,
29988             body, bodyReturn;
29989
29990         function fn(m, name, format, args) {
29991             if (format && useFormat) {
29992                 args = args ? ',' + args: "";
29993                 if (format.substr(0, 5) != "this.") {
29994                     format = "fm." + format + '(';
29995                 }
29996                 else {
29997                     format = 'this.' + format.substr(5) + '(';
29998                 }
29999             }
30000             else {
30001                 args = '';
30002                 format = "(values['" + name + "'] == undefined ? '' : ";
30003             }
30004             return "'," + format + "values['" + name + "']" + args + ") ,'";
30005         }
30006
30007         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
30008         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
30009         eval(body);
30010         return me;
30011     },
30012
30013     /**
30014      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
30015      *
30016      * @param {String/HTMLElement/Ext.Element} el The context element
30017      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30018      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30019      * @return {HTMLElement/Ext.Element} The new node or Element
30020      */
30021     insertFirst: function(el, values, returnElement) {
30022         return this.doInsert('afterBegin', el, values, returnElement);
30023     },
30024
30025     /**
30026      * Applies the supplied values to the template and inserts the new node(s) before el.
30027      *
30028      * @param {String/HTMLElement/Ext.Element} el The context element
30029      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30030      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30031      * @return {HTMLElement/Ext.Element} The new node or Element
30032      */
30033     insertBefore: function(el, values, returnElement) {
30034         return this.doInsert('beforeBegin', el, values, returnElement);
30035     },
30036
30037     /**
30038      * Applies the supplied values to the template and inserts the new node(s) after el.
30039      *
30040      * @param {String/HTMLElement/Ext.Element} el The context element
30041      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30042      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30043      * @return {HTMLElement/Ext.Element} The new node or Element
30044      */
30045     insertAfter: function(el, values, returnElement) {
30046         return this.doInsert('afterEnd', el, values, returnElement);
30047     },
30048
30049     /**
30050      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
30051      *
30052      * For example usage see {@link Ext.Template Ext.Template class docs}.
30053      *
30054      * @param {String/HTMLElement/Ext.Element} el The context element
30055      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30056      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
30057      * @return {HTMLElement/Ext.Element} The new node or Element
30058      */
30059     append: function(el, values, returnElement) {
30060         return this.doInsert('beforeEnd', el, values, returnElement);
30061     },
30062
30063     doInsert: function(where, el, values, returnEl) {
30064         el = Ext.getDom(el);
30065         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
30066         return returnEl ? Ext.get(newNode, true) : newNode;
30067     },
30068
30069     /**
30070      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
30071      *
30072      * @param {String/HTMLElement/Ext.Element} el The context element
30073      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
30074      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
30075      * @return {HTMLElement/Ext.Element} The new node or Element
30076      */
30077     overwrite: function(el, values, returnElement) {
30078         el = Ext.getDom(el);
30079         el.innerHTML = this.applyTemplate(values);
30080         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
30081     }
30082 }, function() {
30083
30084     /**
30085      * @method apply
30086      * @member Ext.Template
30087      * Alias for {@link #applyTemplate}.
30088      * @alias Ext.Template#applyTemplate
30089      */
30090     this.createAlias('apply', 'applyTemplate');
30091 });
30092
30093 /**
30094  * A template class that supports advanced functionality like:
30095  *
30096  * - Autofilling arrays using templates and sub-templates
30097  * - Conditional processing with basic comparison operators
30098  * - Basic math function support
30099  * - Execute arbitrary inline code with special built-in template variables
30100  * - Custom member functions
30101  * - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
30102  *
30103  * XTemplate provides the templating mechanism built into:
30104  *
30105  * - {@link Ext.view.View}
30106  *
30107  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
30108  * demonstrate all of the supported features.
30109  *
30110  * # Sample Data
30111  *
30112  * This is the data object used for reference in each code example:
30113  *
30114  *     var data = {
30115  *         name: 'Tommy Maintz',
30116  *         title: 'Lead Developer',
30117  *         company: 'Sencha Inc.',
30118  *         email: 'tommy@sencha.com',
30119  *         address: '5 Cups Drive',
30120  *         city: 'Palo Alto',
30121  *         state: 'CA',
30122  *         zip: '44102',
30123  *         drinks: ['Coffee', 'Soda', 'Water'],
30124  *         kids: [
30125  *             {
30126  *                 name: 'Joshua',
30127  *                 age:3
30128  *             },
30129  *             {
30130  *                 name: 'Matthew',
30131  *                 age:2
30132  *             },
30133  *             {
30134  *                 name: 'Solomon',
30135  *                 age:0
30136  *             }
30137  *         ]
30138  *     };
30139  *
30140  * # Auto filling of arrays
30141  *
30142  * The **tpl** tag and the **for** operator are used to process the provided data object:
30143  *
30144  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
30145  *   tag for each item in the array.
30146  * - If for="." is specified, the data object provided is examined.
30147  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
30148  *
30149  * Examples:
30150  *
30151  *     <tpl for=".">...</tpl>       // loop through array at root node
30152  *     <tpl for="foo">...</tpl>     // loop through array at foo node
30153  *     <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
30154  *
30155  * Using the sample data above:
30156  *
30157  *     var tpl = new Ext.XTemplate(
30158  *         '<p>Kids: ',
30159  *         '<tpl for=".">',       // process the data.kids node
30160  *             '<p>{#}. {name}</p>',  // use current array index to autonumber
30161  *         '</tpl></p>'
30162  *     );
30163  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
30164  *
30165  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
30166  * object to populate the template:
30167  *
30168  *     var tpl = new Ext.XTemplate(
30169  *         '<p>Name: {name}</p>',
30170  *         '<p>Title: {title}</p>',
30171  *         '<p>Company: {company}</p>',
30172  *         '<p>Kids: ',
30173  *         '<tpl for="kids">',     // interrogate the kids property within the data
30174  *             '<p>{name}</p>',
30175  *         '</tpl></p>'
30176  *     );
30177  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
30178  *
30179  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
30180  * loop. This variable will represent the value of the array at the current index:
30181  *
30182  *     var tpl = new Ext.XTemplate(
30183  *         '<p>{name}\'s favorite beverages:</p>',
30184  *         '<tpl for="drinks">',
30185  *             '<div> - {.}</div>',
30186  *         '</tpl>'
30187  *     );
30188  *     tpl.overwrite(panel.body, data);
30189  *
30190  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
30191  * members via the **parent** object:
30192  *
30193  *     var tpl = new Ext.XTemplate(
30194  *         '<p>Name: {name}</p>',
30195  *         '<p>Kids: ',
30196  *         '<tpl for="kids">',
30197  *             '<tpl if="age &gt; 1">',
30198  *                 '<p>{name}</p>',
30199  *                 '<p>Dad: {parent.name}</p>',
30200  *             '</tpl>',
30201  *         '</tpl></p>'
30202  *     );
30203  *     tpl.overwrite(panel.body, data);
30204  *
30205  * # Conditional processing with basic comparison operators
30206  *
30207  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
30208  * specific parts of the template. Notes:
30209  *
30210  * - Double quotes must be encoded if used within the conditional
30211  * - There is no else operator -- if needed, two opposite if statements should be used.
30212  *
30213  * Examples:
30214  *
30215  *     <tpl if="age > 1 && age < 10">Child</tpl>
30216  *     <tpl if="age >= 10 && age < 18">Teenager</tpl>
30217  *     <tpl if="this.isGirl(name)">...</tpl>
30218  *     <tpl if="id==\'download\'">...</tpl>
30219  *     <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
30220  *     // no good:
30221  *     <tpl if="name == "Tommy"">Hello</tpl>
30222  *     // encode " if it is part of the condition, e.g.
30223  *     <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
30224  *
30225  * Using the sample data above:
30226  *
30227  *     var tpl = new Ext.XTemplate(
30228  *         '<p>Name: {name}</p>',
30229  *         '<p>Kids: ',
30230  *         '<tpl for="kids">',
30231  *             '<tpl if="age &gt; 1">',
30232  *                 '<p>{name}</p>',
30233  *             '</tpl>',
30234  *         '</tpl></p>'
30235  *     );
30236  *     tpl.overwrite(panel.body, data);
30237  *
30238  * # Basic math support
30239  *
30240  * The following basic math operators may be applied directly on numeric data values:
30241  *
30242  *     + - * /
30243  *
30244  * For example:
30245  *
30246  *     var tpl = new Ext.XTemplate(
30247  *         '<p>Name: {name}</p>',
30248  *         '<p>Kids: ',
30249  *         '<tpl for="kids">',
30250  *             '<tpl if="age &gt; 1">',  // <-- Note that the > is encoded
30251  *                 '<p>{#}: {name}</p>',  // <-- Auto-number each item
30252  *                 '<p>In 5 Years: {age+5}</p>',  // <-- Basic math
30253  *                 '<p>Dad: {parent.name}</p>',
30254  *             '</tpl>',
30255  *         '</tpl></p>'
30256  *     );
30257  *     tpl.overwrite(panel.body, data);
30258  *
30259  * # Execute arbitrary inline code with special built-in template variables
30260  *
30261  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
30262  * variables available in that code:
30263  *
30264  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
30265  *   you can change what values is.
30266  * - **parent**: The scope (values) of the ancestor template.
30267  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
30268  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
30269  *
30270  * This example demonstrates basic row striping using an inline code block and the xindex variable:
30271  *
30272  *     var tpl = new Ext.XTemplate(
30273  *         '<p>Name: {name}</p>',
30274  *         '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
30275  *         '<p>Kids: ',
30276  *         '<tpl for="kids">',
30277  *             '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
30278  *             '{name}',
30279  *             '</div>',
30280  *         '</tpl></p>'
30281  *      );
30282  *     tpl.overwrite(panel.body, data);
30283  *
30284  * # Template member functions
30285  *
30286  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
30287  * more complex processing:
30288  *
30289  *     var tpl = new Ext.XTemplate(
30290  *         '<p>Name: {name}</p>',
30291  *         '<p>Kids: ',
30292  *         '<tpl for="kids">',
30293  *             '<tpl if="this.isGirl(name)">',
30294  *                 '<p>Girl: {name} - {age}</p>',
30295  *             '</tpl>',
30296  *              // use opposite if statement to simulate 'else' processing:
30297  *             '<tpl if="this.isGirl(name) == false">',
30298  *                 '<p>Boy: {name} - {age}</p>',
30299  *             '</tpl>',
30300  *             '<tpl if="this.isBaby(age)">',
30301  *                 '<p>{name} is a baby!</p>',
30302  *             '</tpl>',
30303  *         '</tpl></p>',
30304  *         {
30305  *             // XTemplate configuration:
30306  *             disableFormats: true,
30307  *             // member functions:
30308  *             isGirl: function(name){
30309  *                return name == 'Sara Grace';
30310  *             },
30311  *             isBaby: function(age){
30312  *                return age < 1;
30313  *             }
30314  *         }
30315  *     );
30316  *     tpl.overwrite(panel.body, data);
30317  */
30318 Ext.define('Ext.XTemplate', {
30319
30320     /* Begin Definitions */
30321
30322     extend: 'Ext.Template',
30323
30324     /* End Definitions */
30325
30326     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
30327     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
30328     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
30329     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
30330     constructor: function() {
30331         this.callParent(arguments);
30332
30333         var me = this,
30334             html = me.html,
30335             argsRe = me.argsRe,
30336             nameRe = me.nameRe,
30337             ifRe = me.ifRe,
30338             execRe = me.execRe,
30339             id = 0,
30340             tpls = [],
30341             VALUES = 'values',
30342             PARENT = 'parent',
30343             XINDEX = 'xindex',
30344             XCOUNT = 'xcount',
30345             RETURN = 'return ',
30346             WITHVALUES = 'with(values){ ',
30347             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
30348
30349         html = ['<tpl>', html, '</tpl>'].join('');
30350
30351         while ((m = html.match(argsRe))) {
30352             exp = null;
30353             fn = null;
30354             exec = null;
30355             matchName = m[0].match(nameRe);
30356             matchIf = m[0].match(ifRe);
30357             matchExec = m[0].match(execRe);
30358
30359             exp = matchIf ? matchIf[1] : null;
30360             if (exp) {
30361                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
30362             }
30363
30364             exp = matchExec ? matchExec[1] : null;
30365             if (exp) {
30366                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
30367             }
30368
30369             name = matchName ? matchName[1] : null;
30370             if (name) {
30371                 if (name === '.') {
30372                     name = VALUES;
30373                 } else if (name === '..') {
30374                     name = PARENT;
30375                 }
30376                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
30377             }
30378
30379             tpls.push({
30380                 id: id,
30381                 target: name,
30382                 exec: exec,
30383                 test: fn,
30384                 body: m[1] || ''
30385             });
30386
30387             html = html.replace(m[0], '{xtpl' + id + '}');
30388             id = id + 1;
30389         }
30390
30391         for (i = tpls.length - 1; i >= 0; --i) {
30392             me.compileTpl(tpls[i]);
30393         }
30394         me.master = tpls[tpls.length - 1];
30395         me.tpls = tpls;
30396     },
30397
30398     // @private
30399     applySubTemplate: function(id, values, parent, xindex, xcount) {
30400         var me = this, t = me.tpls[id];
30401         return t.compiled.call(me, values, parent, xindex, xcount);
30402     },
30403
30404     /**
30405      * @cfg {RegExp} codeRe
30406      * The regular expression used to match code variables. Default: matches {[expression]}.
30407      */
30408     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
30409
30410     /**
30411      * @cfg {Boolean} compiled
30412      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
30413      */
30414
30415     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
30416
30417     // @private
30418     compileTpl: function(tpl) {
30419         var fm = Ext.util.Format,
30420             me = this,
30421             useFormat = me.disableFormats !== true,
30422             body, bodyReturn, evaluatedFn;
30423
30424         function fn(m, name, format, args, math) {
30425             var v;
30426             // name is what is inside the {}
30427             // Name begins with xtpl, use a Sub Template
30428             if (name.substr(0, 4) == 'xtpl') {
30429                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
30430             }
30431             // name = "." - Just use the values object.
30432             if (name == '.') {
30433                 // filter to not include arrays/objects/nulls
30434                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
30435             }
30436
30437             // name = "#" - Use the xindex
30438             else if (name == '#') {
30439                 v = 'xindex';
30440             }
30441             else if (name.substr(0, 7) == "parent.") {
30442                 v = name;
30443             }
30444             // name has a . in it - Use object literal notation, starting from values
30445             else if (name.indexOf('.') != -1) {
30446                 v = "values." + name;
30447             }
30448
30449             // name is a property of values
30450             else {
30451                 v = "values['" + name + "']";
30452             }
30453             if (math) {
30454                 v = '(' + v + math + ')';
30455             }
30456             if (format && useFormat) {
30457                 args = args ? ',' + args : "";
30458                 if (format.substr(0, 5) != "this.") {
30459                     format = "fm." + format + '(';
30460                 }
30461                 else {
30462                     format = 'this.' + format.substr(5) + '(';
30463                 }
30464             }
30465             else {
30466                 args = '';
30467                 format = "(" + v + " === undefined ? '' : ";
30468             }
30469             return "'," + format + v + args + "),'";
30470         }
30471
30472         function codeFn(m, code) {
30473             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
30474             return "',(" + code.replace(me.compileARe, "'") + "),'";
30475         }
30476
30477         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
30478         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
30479         eval(body);
30480
30481         tpl.compiled = function(values, parent, xindex, xcount) {
30482             var vs,
30483                 length,
30484                 buffer,
30485                 i;
30486
30487             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
30488                 return '';
30489             }
30490
30491             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
30492             if (!vs) {
30493                return '';
30494             }
30495
30496             parent = tpl.target ? values : parent;
30497             if (tpl.target && Ext.isArray(vs)) {
30498                 buffer = [];
30499                 length = vs.length;
30500                 if (tpl.exec) {
30501                     for (i = 0; i < length; i++) {
30502                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
30503                         tpl.exec.call(me, vs[i], parent, i + 1, length);
30504                     }
30505                 } else {
30506                     for (i = 0; i < length; i++) {
30507                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
30508                     }
30509                 }
30510                 return buffer.join('');
30511             }
30512
30513             if (tpl.exec) {
30514                 tpl.exec.call(me, vs, parent, xindex, xcount);
30515             }
30516             return evaluatedFn.call(me, vs, parent, xindex, xcount);
30517         };
30518
30519         return this;
30520     },
30521
30522     // inherit docs from Ext.Template
30523     applyTemplate: function(values) {
30524         return this.master.compiled.call(this, values, {}, 1, 1);
30525     },
30526
30527     /**
30528      * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
30529      * @return {Ext.XTemplate} this
30530      */
30531     compile: function() {
30532         return this;
30533     }
30534 }, function() {
30535     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
30536     this.createAlias('apply', 'applyTemplate');
30537 });
30538
30539 /**
30540  * @class Ext.app.Controller
30541  *
30542  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
30543  * views) and take some action. Here's how we might create a Controller to manage Users:
30544  *
30545  *     Ext.define('MyApp.controller.Users', {
30546  *         extend: 'Ext.app.Controller',
30547  *
30548  *         init: function() {
30549  *             console.log('Initialized Users! This happens before the Application launch function is called');
30550  *         }
30551  *     });
30552  *
30553  * The init function is a special method that is called when your application boots. It is called before the
30554  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30555  * your Viewport is created.
30556  *
30557  * The init function is a great place to set up how your controller interacts with the view, and is usually used in
30558  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
30559  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
30560  * our Users controller to tell us when the panel is rendered:
30561  *
30562  *     Ext.define('MyApp.controller.Users', {
30563  *         extend: 'Ext.app.Controller',
30564  *
30565  *         init: function() {
30566  *             this.control({
30567  *                 'viewport > panel': {
30568  *                     render: this.onPanelRendered
30569  *                 }
30570  *             });
30571  *         },
30572  *
30573  *         onPanelRendered: function() {
30574  *             console.log('The panel was rendered');
30575  *         }
30576  *     });
30577  *
30578  * We've updated the init function to use this.control to set up listeners on views in our application. The control
30579  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
30580  * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
30581  * it allows us to pass a CSS-like selector that will find every matching component on the page.
30582  *
30583  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
30584  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
30585  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
30586  * onPanelRendered function is called.
30587  *
30588  * <u>Using refs</u>
30589  *
30590  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
30591  * make it really easy to get references to Views on your page. Let's look at an example of this now:
30592  *
30593  *     Ext.define('MyApp.controller.Users', {
30594  *         extend: 'Ext.app.Controller',
30595  *
30596  *         refs: [
30597  *             {
30598  *                 ref: 'list',
30599  *                 selector: 'grid'
30600  *             }
30601  *         ],
30602  *
30603  *         init: function() {
30604  *             this.control({
30605  *                 'button': {
30606  *                     click: this.refreshGrid
30607  *                 }
30608  *             });
30609  *         },
30610  *
30611  *         refreshGrid: function() {
30612  *             this.getList().store.load();
30613  *         }
30614  *     });
30615  *
30616  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
30617  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
30618  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30619  * assigns it to the reference 'list'.
30620  *
30621  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30622  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
30623  * was capitalized and prepended with get to go from 'list' to 'getList'.
30624  *
30625  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30626  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
30627  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30628  * match a single View in your application (in the case above our selector will match any grid on the page).
30629  *
30630  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30631  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
30632  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30633  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30634  *
30635  * You can create any number of refs and control any number of components this way, simply adding more functions to
30636  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
30637  * examples/app/feed-viewer folder in the SDK download.
30638  *
30639  * <u>Generated getter methods</u>
30640  *
30641  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
30642  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30643  *
30644  *     Ext.define('MyApp.controller.Users', {
30645  *         extend: 'Ext.app.Controller',
30646  *
30647  *         models: ['User'],
30648  *         stores: ['AllUsers', 'AdminUsers'],
30649  *
30650  *         init: function() {
30651  *             var User = this.getUserModel(),
30652  *                 allUsers = this.getAllUsersStore();
30653  *
30654  *             var ed = new User({name: 'Ed'});
30655  *             allUsers.add(ed);
30656  *         }
30657  *     });
30658  *
30659  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30660  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
30661  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30662  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
30663  * functionality.
30664  *
30665  * <u>Further Reading</u>
30666  *
30667  * For more information about writing Ext JS 4 applications, please see the
30668  * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
30669  *
30670  * @docauthor Ed Spencer
30671  */
30672 Ext.define('Ext.app.Controller', {
30673
30674     mixins: {
30675         observable: 'Ext.util.Observable'
30676     },
30677
30678     /**
30679      * @cfg {String} id The id of this controller. You can use this id when dispatching.
30680      */
30681     
30682     /**
30683      * @cfg {String[]} models
30684      * Array of models to require from AppName.model namespace. For example:
30685      * 
30686      *     Ext.define("MyApp.controller.Foo", {
30687      *         extend: "Ext.app.Controller",
30688      *         models: ['User', 'Vehicle']
30689      *     });
30690      * 
30691      * This is equivalent of:
30692      * 
30693      *     Ext.define("MyApp.controller.Foo", {
30694      *         extend: "Ext.app.Controller",
30695      *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
30696      *     });
30697      * 
30698      */
30699
30700     /**
30701      * @cfg {String[]} views
30702      * Array of views to require from AppName.view namespace. For example:
30703      * 
30704      *     Ext.define("MyApp.controller.Foo", {
30705      *         extend: "Ext.app.Controller",
30706      *         views: ['List', 'Detail']
30707      *     });
30708      * 
30709      * This is equivalent of:
30710      * 
30711      *     Ext.define("MyApp.controller.Foo", {
30712      *         extend: "Ext.app.Controller",
30713      *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
30714      *     });
30715      * 
30716      */
30717
30718     /**
30719      * @cfg {String[]} stores
30720      * Array of stores to require from AppName.store namespace. For example:
30721      * 
30722      *     Ext.define("MyApp.controller.Foo", {
30723      *         extend: "Ext.app.Controller",
30724      *         stores: ['Users', 'Vehicles']
30725      *     });
30726      * 
30727      * This is equivalent of:
30728      * 
30729      *     Ext.define("MyApp.controller.Foo", {
30730      *         extend: "Ext.app.Controller",
30731      *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
30732      *     });
30733      * 
30734      */
30735
30736     onClassExtended: function(cls, data) {
30737         var className = Ext.getClassName(cls),
30738             match = className.match(/^(.*)\.controller\./);
30739
30740         if (match !== null) {
30741             var namespace = Ext.Loader.getPrefix(className) || match[1],
30742                 onBeforeClassCreated = data.onBeforeClassCreated,
30743                 requires = [],
30744                 modules = ['model', 'view', 'store'],
30745                 prefix;
30746
30747             data.onBeforeClassCreated = function(cls, data) {
30748                 var i, ln, module,
30749                     items, j, subLn, item;
30750
30751                 for (i = 0,ln = modules.length; i < ln; i++) {
30752                     module = modules[i];
30753
30754                     items = Ext.Array.from(data[module + 's']);
30755
30756                     for (j = 0,subLn = items.length; j < subLn; j++) {
30757                         item = items[j];
30758
30759                         prefix = Ext.Loader.getPrefix(item);
30760
30761                         if (prefix === '' || prefix === item) {
30762                             requires.push(namespace + '.' + module + '.' + item);
30763                         }
30764                         else {
30765                             requires.push(item);
30766                         }
30767                     }
30768                 }
30769
30770                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30771             };
30772         }
30773     },
30774
30775     /**
30776      * Creates new Controller.
30777      * @param {Object} config (optional) Config object.
30778      */
30779     constructor: function(config) {
30780         this.mixins.observable.constructor.call(this, config);
30781
30782         Ext.apply(this, config || {});
30783
30784         this.createGetters('model', this.models);
30785         this.createGetters('store', this.stores);
30786         this.createGetters('view', this.views);
30787
30788         if (this.refs) {
30789             this.ref(this.refs);
30790         }
30791     },
30792
30793     /**
30794      * A template method that is called when your application boots. It is called before the
30795      * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30796      * your Viewport is created.
30797      * 
30798      * @param {Ext.app.Application} application
30799      * @template
30800      */
30801     init: function(application) {},
30802
30803     /**
30804      * A template method like {@link #init}, but called after the viewport is created.
30805      * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
30806      * 
30807      * @param {Ext.app.Application} application
30808      * @template
30809      */
30810     onLaunch: function(application) {},
30811
30812     createGetters: function(type, refs) {
30813         type = Ext.String.capitalize(type);
30814         Ext.Array.each(refs, function(ref) {
30815             var fn = 'get',
30816                 parts = ref.split('.');
30817
30818             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30819             Ext.Array.each(parts, function(part) {
30820                 fn += Ext.String.capitalize(part);
30821             });
30822             fn += type;
30823
30824             if (!this[fn]) {
30825                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30826             }
30827             // Execute it right away
30828             this[fn](ref);
30829         },
30830         this);
30831     },
30832
30833     ref: function(refs) {
30834         var me = this;
30835         refs = Ext.Array.from(refs);
30836         Ext.Array.each(refs, function(info) {
30837             var ref = info.ref,
30838                 fn = 'get' + Ext.String.capitalize(ref);
30839             if (!me[fn]) {
30840                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30841             }
30842         });
30843     },
30844
30845     getRef: function(ref, info, config) {
30846         this.refCache = this.refCache || {};
30847         info = info || {};
30848         config = config || {};
30849
30850         Ext.apply(info, config);
30851
30852         if (info.forceCreate) {
30853             return Ext.ComponentManager.create(info, 'component');
30854         }
30855
30856         var me = this,
30857             selector = info.selector,
30858             cached = me.refCache[ref];
30859
30860         if (!cached) {
30861             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30862             if (!cached && info.autoCreate) {
30863                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30864             }
30865             if (cached) {
30866                 cached.on('beforedestroy', function() {
30867                     me.refCache[ref] = null;
30868                 });
30869             }
30870         }
30871
30872         return cached;
30873     },
30874
30875     /**
30876      * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
30877      * object containing component paths mapped to a hash of listener functions.
30878      *
30879      * In the following example the `updateUser` function is mapped to to the `click`
30880      * event on a button component, which is a child of the `useredit` component.
30881      *
30882      *     Ext.define('AM.controller.Users', {
30883      *         init: function() {
30884      *             this.control({
30885      *                 'useredit button[action=save]': {
30886      *                     click: this.updateUser
30887      *                 }
30888      *             });
30889      *         },
30890      *
30891      *         updateUser: function(button) {
30892      *             console.log('clicked the Save button');
30893      *         }
30894      *     });
30895      *
30896      * See {@link Ext.ComponentQuery} for more information on component selectors.
30897      *
30898      * @param {String/Object} selectors If a String, the second argument is used as the
30899      * listeners, otherwise an object of selectors -> listeners is assumed
30900      * @param {Object} listeners
30901      */
30902     control: function(selectors, listeners) {
30903         this.application.control(selectors, listeners, this);
30904     },
30905
30906     /**
30907      * Returns instance of a {@link Ext.app.Controller controller} with the given name.
30908      * When controller doesn't exist yet, it's created.
30909      * @param {String} name
30910      * @return {Ext.app.Controller} a controller instance.
30911      */
30912     getController: function(name) {
30913         return this.application.getController(name);
30914     },
30915
30916     /**
30917      * Returns instance of a {@link Ext.data.Store Store} with the given name.
30918      * When store doesn't exist yet, it's created.
30919      * @param {String} name
30920      * @return {Ext.data.Store} a store instance.
30921      */
30922     getStore: function(name) {
30923         return this.application.getStore(name);
30924     },
30925
30926     /**
30927      * Returns a {@link Ext.data.Model Model} class with the given name.
30928      * A shorthand for using {@link Ext.ModelManager#getModel}.
30929      * @param {String} name
30930      * @return {Ext.data.Model} a model class.
30931      */
30932     getModel: function(model) {
30933         return this.application.getModel(model);
30934     },
30935
30936     /**
30937      * Returns a View class with the given name.  To create an instance of the view,
30938      * you can use it like it's used by Application to create the Viewport:
30939      * 
30940      *     this.getView('Viewport').create();
30941      * 
30942      * @param {String} name
30943      * @return {Ext.Base} a view class.
30944      */
30945     getView: function(view) {
30946         return this.application.getView(view);
30947     }
30948 });
30949
30950 /**
30951  * @author Don Griffin
30952  *
30953  * This class is a base for all id generators. It also provides lookup of id generators by
30954  * their id.
30955  * 
30956  * Generally, id generators are used to generate a primary key for new model instances. There
30957  * are different approaches to solving this problem, so this mechanism has both simple use
30958  * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
30959  * using the {@link Ext.data.Model#idgen} property.
30960  *
30961  * # Identity, Type and Shared IdGenerators
30962  *
30963  * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
30964  * This is done by giving IdGenerator instances an id property by which they can be looked
30965  * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
30966  * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
30967  * assign them the same id:
30968  *
30969  *     Ext.define('MyApp.data.MyModelA', {
30970  *         extend: 'Ext.data.Model',
30971  *         idgen: {
30972  *             type: 'sequential',
30973  *             id: 'foo'
30974  *         }
30975  *     });
30976  *
30977  *     Ext.define('MyApp.data.MyModelB', {
30978  *         extend: 'Ext.data.Model',
30979  *         idgen: {
30980  *             type: 'sequential',
30981  *             id: 'foo'
30982  *         }
30983  *     });
30984  *
30985  * To make this as simple as possible for generator types that are shared by many (or all)
30986  * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
30987  * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
30988  * to its type ('uuid'). In other words, the following Models share the same generator:
30989  *
30990  *     Ext.define('MyApp.data.MyModelX', {
30991  *         extend: 'Ext.data.Model',
30992  *         idgen: 'uuid'
30993  *     });
30994  *
30995  *     Ext.define('MyApp.data.MyModelY', {
30996  *         extend: 'Ext.data.Model',
30997  *         idgen: 'uuid'
30998  *     });
30999  *
31000  * This can be overridden (by specifying the id explicitly), but there is no particularly
31001  * good reason to do so for this generator type.
31002  *
31003  * # Creating Custom Generators
31004  * 
31005  * An id generator should derive from this class and implement the {@link #generate} method.
31006  * The constructor will apply config properties on new instances, so a constructor is often
31007  * not necessary.
31008  *
31009  * To register an id generator type, a derived class should provide an `alias` like so:
31010  *
31011  *     Ext.define('MyApp.data.CustomIdGenerator', {
31012  *         extend: 'Ext.data.IdGenerator',
31013  *         alias: 'idgen.custom',
31014  *
31015  *         configProp: 42, // some config property w/default value
31016  *
31017  *         generate: function () {
31018  *             return ... // a new id
31019  *         }
31020  *     });
31021  *
31022  * Using the custom id generator is then straightforward:
31023  *
31024  *     Ext.define('MyApp.data.MyModel', {
31025  *         extend: 'Ext.data.Model',
31026  *         idgen: 'custom'
31027  *     });
31028  *     // or...
31029  *
31030  *     Ext.define('MyApp.data.MyModel', {
31031  *         extend: 'Ext.data.Model',
31032  *         idgen: {
31033  *             type: 'custom',
31034  *             configProp: value
31035  *         }
31036  *     });
31037  *
31038  * It is not recommended to mix shared generators with generator configuration. This leads
31039  * to unpredictable results unless all configurations match (which is also redundant). In
31040  * such cases, a custom generator with a default id is the best approach.
31041  *
31042  *     Ext.define('MyApp.data.CustomIdGenerator', {
31043  *         extend: 'Ext.data.SequentialIdGenerator',
31044  *         alias: 'idgen.custom',
31045  *
31046  *         id: 'custom', // shared by default
31047  *
31048  *         prefix: 'ID_',
31049  *         seed: 1000
31050  *     });
31051  *
31052  *     Ext.define('MyApp.data.MyModelX', {
31053  *         extend: 'Ext.data.Model',
31054  *         idgen: 'custom'
31055  *     });
31056  *
31057  *     Ext.define('MyApp.data.MyModelY', {
31058  *         extend: 'Ext.data.Model',
31059  *         idgen: 'custom'
31060  *     });
31061  *
31062  *     // the above models share a generator that produces ID_1000, ID_1001, etc..
31063  *
31064  */
31065 Ext.define('Ext.data.IdGenerator', {
31066
31067     isGenerator: true,
31068
31069     /**
31070      * Initializes a new instance.
31071      * @param {Object} config (optional) Configuration object to be applied to the new instance.
31072      */
31073     constructor: function(config) {
31074         var me = this;
31075
31076         Ext.apply(me, config);
31077
31078         if (me.id) {
31079             Ext.data.IdGenerator.all[me.id] = me;
31080         }
31081     },
31082
31083     /**
31084      * @cfg {String} id
31085      * The id by which to register a new instance. This instance can be found using the
31086      * {@link Ext.data.IdGenerator#get} static method.
31087      */
31088
31089     getRecId: function (rec) {
31090         return rec.modelName + '-' + rec.internalId;
31091     },
31092
31093     /**
31094      * Generates and returns the next id. This method must be implemented by the derived
31095      * class.
31096      *
31097      * @return {String} The next id.
31098      * @method generate
31099      * @abstract
31100      */
31101
31102     statics: {
31103         /**
31104          * @property {Object} all
31105          * This object is keyed by id to lookup instances.
31106          * @private
31107          * @static
31108          */
31109         all: {},
31110
31111         /**
31112          * Returns the IdGenerator given its config description.
31113          * @param {String/Object} config If this parameter is an IdGenerator instance, it is
31114          * simply returned. If this is a string, it is first used as an id for lookup and
31115          * then, if there is no match, as a type to create a new instance. This parameter
31116          * can also be a config object that contains a `type` property (among others) that
31117          * are used to create and configure the instance.
31118          * @static
31119          */
31120         get: function (config) {
31121             var generator,
31122                 id,
31123                 type;
31124
31125             if (typeof config == 'string') {
31126                 id = type = config;
31127                 config = null;
31128             } else if (config.isGenerator) {
31129                 return config;
31130             } else {
31131                 id = config.id || config.type;
31132                 type = config.type;
31133             }
31134
31135             generator = this.all[id];
31136             if (!generator) {
31137                 generator = Ext.create('idgen.' + type, config);
31138             }
31139
31140             return generator;
31141         }
31142     }
31143 });
31144
31145 /**
31146  * @class Ext.data.SortTypes
31147  * This class defines a series of static methods that are used on a
31148  * {@link Ext.data.Field} for performing sorting. The methods cast the 
31149  * underlying values into a data type that is appropriate for sorting on
31150  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
31151  * the sortType will be set to a sane default if the sortType is not 
31152  * explicitly defined on the field. The sortType will make any necessary
31153  * modifications to the value and return it.
31154  * <ul>
31155  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
31156  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
31157  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
31158  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
31159  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
31160  * <li><b>asInt</b> - Converts the value to an integer number</li>
31161  * </ul>
31162  * <p>
31163  * It is also possible to create a custom sortType that can be used throughout
31164  * an application.
31165  * <pre><code>
31166 Ext.apply(Ext.data.SortTypes, {
31167     asPerson: function(person){
31168         // expects an object with a first and last name property
31169         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
31170     }    
31171 });
31172
31173 Ext.define('Employee', {
31174     extend: 'Ext.data.Model',
31175     fields: [{
31176         name: 'person',
31177         sortType: 'asPerson'
31178     }, {
31179         name: 'salary',
31180         type: 'float' // sortType set to asFloat
31181     }]
31182 });
31183  * </code></pre>
31184  * </p>
31185  * @singleton
31186  * @docauthor Evan Trimboli <evan@sencha.com>
31187  */
31188 Ext.define('Ext.data.SortTypes', {
31189     
31190     singleton: true,
31191     
31192     /**
31193      * Default sort that does nothing
31194      * @param {Object} s The value being converted
31195      * @return {Object} The comparison value
31196      */
31197     none : function(s) {
31198         return s;
31199     },
31200
31201     /**
31202      * The regular expression used to strip tags
31203      * @type {RegExp}
31204      * @property
31205      */
31206     stripTagsRE : /<\/?[^>]+>/gi,
31207
31208     /**
31209      * Strips all HTML tags to sort on text only
31210      * @param {Object} s The value being converted
31211      * @return {String} The comparison value
31212      */
31213     asText : function(s) {
31214         return String(s).replace(this.stripTagsRE, "");
31215     },
31216
31217     /**
31218      * Strips all HTML tags to sort on text only - Case insensitive
31219      * @param {Object} s The value being converted
31220      * @return {String} The comparison value
31221      */
31222     asUCText : function(s) {
31223         return String(s).toUpperCase().replace(this.stripTagsRE, "");
31224     },
31225
31226     /**
31227      * Case insensitive string
31228      * @param {Object} s The value being converted
31229      * @return {String} The comparison value
31230      */
31231     asUCString : function(s) {
31232         return String(s).toUpperCase();
31233     },
31234
31235     /**
31236      * Date sorting
31237      * @param {Object} s The value being converted
31238      * @return {Number} The comparison value
31239      */
31240     asDate : function(s) {
31241         if(!s){
31242             return 0;
31243         }
31244         if(Ext.isDate(s)){
31245             return s.getTime();
31246         }
31247         return Date.parse(String(s));
31248     },
31249
31250     /**
31251      * Float sorting
31252      * @param {Object} s The value being converted
31253      * @return {Number} The comparison value
31254      */
31255     asFloat : function(s) {
31256         var val = parseFloat(String(s).replace(/,/g, ""));
31257         return isNaN(val) ? 0 : val;
31258     },
31259
31260     /**
31261      * Integer sorting
31262      * @param {Object} s The value being converted
31263      * @return {Number} The comparison value
31264      */
31265     asInt : function(s) {
31266         var val = parseInt(String(s).replace(/,/g, ""), 10);
31267         return isNaN(val) ? 0 : val;
31268     }
31269 });
31270 /**
31271  * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
31272  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
31273  * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
31274  * on their records. Example usage:
31275  *
31276  *     //set up a fictional MixedCollection containing a few people to filter on
31277  *     var allNames = new Ext.util.MixedCollection();
31278  *     allNames.addAll([
31279  *         {id: 1, name: 'Ed',    age: 25},
31280  *         {id: 2, name: 'Jamie', age: 37},
31281  *         {id: 3, name: 'Abe',   age: 32},
31282  *         {id: 4, name: 'Aaron', age: 26},
31283  *         {id: 5, name: 'David', age: 32}
31284  *     ]);
31285  *
31286  *     var ageFilter = new Ext.util.Filter({
31287  *         property: 'age',
31288  *         value   : 32
31289  *     });
31290  *
31291  *     var longNameFilter = new Ext.util.Filter({
31292  *         filterFn: function(item) {
31293  *             return item.name.length > 4;
31294  *         }
31295  *     });
31296  *
31297  *     //a new MixedCollection with the 3 names longer than 4 characters
31298  *     var longNames = allNames.filter(longNameFilter);
31299  *
31300  *     //a new MixedCollection with the 2 people of age 24:
31301  *     var youngFolk = allNames.filter(ageFilter);
31302  *
31303  */
31304 Ext.define('Ext.util.Filter', {
31305
31306     /* Begin Definitions */
31307
31308     /* End Definitions */
31309     /**
31310      * @cfg {String} property
31311      * The property to filter on. Required unless a {@link #filterFn} is passed
31312      */
31313     
31314     /**
31315      * @cfg {Function} filterFn
31316      * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
31317      * true to accept each item or false to reject it
31318      */
31319     
31320     /**
31321      * @cfg {Boolean} anyMatch
31322      * True to allow any match - no regex start/end line anchors will be added.
31323      */
31324     anyMatch: false,
31325     
31326     /**
31327      * @cfg {Boolean} exactMatch
31328      * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
31329      */
31330     exactMatch: false,
31331     
31332     /**
31333      * @cfg {Boolean} caseSensitive
31334      * True to make the regex case sensitive (adds 'i' switch to regex).
31335      */
31336     caseSensitive: false,
31337     
31338     /**
31339      * @cfg {String} root
31340      * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
31341      * make the filter pull the {@link #property} out of the data object of each item
31342      */
31343
31344     /**
31345      * Creates new Filter.
31346      * @param {Object} [config] Config object
31347      */
31348     constructor: function(config) {
31349         var me = this;
31350         Ext.apply(me, config);
31351         
31352         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
31353         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
31354         me.filter = me.filter || me.filterFn;
31355         
31356         if (me.filter === undefined) {
31357             if (me.property === undefined || me.value === undefined) {
31358                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
31359                 // Model has been updated to allow string ids
31360                 
31361                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
31362             } else {
31363                 me.filter = me.createFilterFn();
31364             }
31365             
31366             me.filterFn = me.filter;
31367         }
31368     },
31369     
31370     /**
31371      * @private
31372      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
31373      */
31374     createFilterFn: function() {
31375         var me       = this,
31376             matcher  = me.createValueMatcher(),
31377             property = me.property;
31378         
31379         return function(item) {
31380             var value = me.getRoot.call(me, item)[property];
31381             return matcher === null ? value === null : matcher.test(value);
31382         };
31383     },
31384     
31385     /**
31386      * @private
31387      * Returns the root property of the given item, based on the configured {@link #root} property
31388      * @param {Object} item The item
31389      * @return {Object} The root property of the object
31390      */
31391     getRoot: function(item) {
31392         var root = this.root;
31393         return root === undefined ? item : item[root];
31394     },
31395     
31396     /**
31397      * @private
31398      * Returns a regular expression based on the given value and matching options
31399      */
31400     createValueMatcher : function() {
31401         var me            = this,
31402             value         = me.value,
31403             anyMatch      = me.anyMatch,
31404             exactMatch    = me.exactMatch,
31405             caseSensitive = me.caseSensitive,
31406             escapeRe      = Ext.String.escapeRegex;
31407             
31408         if (value === null) {
31409             return value;
31410         }
31411         
31412         if (!value.exec) { // not a regex
31413             value = String(value);
31414
31415             if (anyMatch === true) {
31416                 value = escapeRe(value);
31417             } else {
31418                 value = '^' + escapeRe(value);
31419                 if (exactMatch === true) {
31420                     value += '$';
31421                 }
31422             }
31423             value = new RegExp(value, caseSensitive ? '' : 'i');
31424          }
31425          
31426          return value;
31427     }
31428 });
31429 /**
31430  * Represents a single sorter that can be applied to a Store. The sorter is used
31431  * to compare two values against each other for the purpose of ordering them. Ordering
31432  * is achieved by specifying either:
31433  *
31434  * - {@link #property A sorting property}
31435  * - {@link #sorterFn A sorting function}
31436  *
31437  * As a contrived example, we can specify a custom sorter that sorts by rank:
31438  *
31439  *     Ext.define('Person', {
31440  *         extend: 'Ext.data.Model',
31441  *         fields: ['name', 'rank']
31442  *     });
31443  *
31444  *     Ext.create('Ext.data.Store', {
31445  *         model: 'Person',
31446  *         proxy: 'memory',
31447  *         sorters: [{
31448  *             sorterFn: function(o1, o2){
31449  *                 var getRank = function(o){
31450  *                     var name = o.get('rank');
31451  *                     if (name === 'first') {
31452  *                         return 1;
31453  *                     } else if (name === 'second') {
31454  *                         return 2;
31455  *                     } else {
31456  *                         return 3;
31457  *                     }
31458  *                 },
31459  *                 rank1 = getRank(o1),
31460  *                 rank2 = getRank(o2);
31461  *
31462  *                 if (rank1 === rank2) {
31463  *                     return 0;
31464  *                 }
31465  *
31466  *                 return rank1 < rank2 ? -1 : 1;
31467  *             }
31468  *         }],
31469  *         data: [{
31470  *             name: 'Person1',
31471  *             rank: 'second'
31472  *         }, {
31473  *             name: 'Person2',
31474  *             rank: 'third'
31475  *         }, {
31476  *             name: 'Person3',
31477  *             rank: 'first'
31478  *         }]
31479  *     });
31480  */
31481 Ext.define('Ext.util.Sorter', {
31482
31483     /**
31484      * @cfg {String} property
31485      * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
31486      * directly and compared for sorting using the built in comparison operators.
31487      */
31488     
31489     /**
31490      * @cfg {Function} sorterFn
31491      * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
31492      * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
31493      * function should return:
31494      *
31495      *   - -1 if o1 is "less than" o2
31496      *   - 0 if o1 is "equal" to o2
31497      *   - 1 if o1 is "greater than" o2
31498      */
31499     
31500     /**
31501      * @cfg {String} root
31502      * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
31503      * make the filter pull the {@link #property} out of the data object of each item
31504      */
31505     
31506     /**
31507      * @cfg {Function} transform
31508      * A function that will be run on each value before it is compared in the sorter. The function will receive a single
31509      * argument, the value.
31510      */
31511     
31512     /**
31513      * @cfg {String} direction
31514      * The direction to sort by.
31515      */
31516     direction: "ASC",
31517     
31518     constructor: function(config) {
31519         var me = this;
31520         
31521         Ext.apply(me, config);
31522         
31523         if (me.property === undefined && me.sorterFn === undefined) {
31524             Ext.Error.raise("A Sorter requires either a property or a sorter function");
31525         }
31526         
31527         me.updateSortFunction();
31528     },
31529     
31530     /**
31531      * @private
31532      * Creates and returns a function which sorts an array by the given property and direction
31533      * @return {Function} A function which sorts by the property/direction combination provided
31534      */
31535     createSortFunction: function(sorterFn) {
31536         var me        = this,
31537             property  = me.property,
31538             direction = me.direction || "ASC",
31539             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
31540         
31541         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
31542         //-1 if object 2 is greater or 0 if they are equal
31543         return function(o1, o2) {
31544             return modifier * sorterFn.call(me, o1, o2);
31545         };
31546     },
31547     
31548     /**
31549      * @private
31550      * Basic default sorter function that just compares the defined property of each object
31551      */
31552     defaultSorterFn: function(o1, o2) {
31553         var me = this,
31554             transform = me.transform,
31555             v1 = me.getRoot(o1)[me.property],
31556             v2 = me.getRoot(o2)[me.property];
31557             
31558         if (transform) {
31559             v1 = transform(v1);
31560             v2 = transform(v2);
31561         }
31562
31563         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
31564     },
31565     
31566     /**
31567      * @private
31568      * Returns the root property of the given item, based on the configured {@link #root} property
31569      * @param {Object} item The item
31570      * @return {Object} The root property of the object
31571      */
31572     getRoot: function(item) {
31573         return this.root === undefined ? item : item[this.root];
31574     },
31575     
31576     /**
31577      * Set the sorting direction for this sorter.
31578      * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
31579      */
31580     setDirection: function(direction) {
31581         var me = this;
31582         me.direction = direction;
31583         me.updateSortFunction();
31584     },
31585     
31586     /**
31587      * Toggles the sorting direction for this sorter.
31588      */
31589     toggle: function() {
31590         var me = this;
31591         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
31592         me.updateSortFunction();
31593     },
31594     
31595     /**
31596      * Update the sort function for this sorter.
31597      * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
31598      * sorting function.
31599      */
31600     updateSortFunction: function(fn) {
31601         var me = this;
31602         fn = fn || me.sorterFn || me.defaultSorterFn;
31603         me.sort = me.createSortFunction(fn);
31604     }
31605 });
31606 /**
31607  * @author Ed Spencer
31608  *
31609  * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
31610  * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
31611  * Operation objects directly.
31612  *
31613  * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
31614  */
31615 Ext.define('Ext.data.Operation', {
31616     /**
31617      * @cfg {Boolean} synchronous
31618      * True if this Operation is to be executed synchronously. This property is inspected by a
31619      * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
31620      */
31621     synchronous: true,
31622
31623     /**
31624      * @cfg {String} action
31625      * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
31626      */
31627     action: undefined,
31628
31629     /**
31630      * @cfg {Ext.util.Filter[]} filters
31631      * Optional array of filter objects. Only applies to 'read' actions.
31632      */
31633     filters: undefined,
31634
31635     /**
31636      * @cfg {Ext.util.Sorter[]} sorters
31637      * Optional array of sorter objects. Only applies to 'read' actions.
31638      */
31639     sorters: undefined,
31640
31641     /**
31642      * @cfg {Ext.util.Grouper} group
31643      * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
31644      */
31645     group: undefined,
31646
31647     /**
31648      * @cfg {Number} start
31649      * The start index (offset), used in paging when running a 'read' action.
31650      */
31651     start: undefined,
31652
31653     /**
31654      * @cfg {Number} limit
31655      * The number of records to load. Used on 'read' actions when paging is being used.
31656      */
31657     limit: undefined,
31658
31659     /**
31660      * @cfg {Ext.data.Batch} batch
31661      * The batch that this Operation is a part of.
31662      */
31663     batch: undefined,
31664
31665     /**
31666      * @cfg {Function} callback
31667      * Function to execute when operation completed.  Will be called with the following parameters:
31668      *
31669      * - records : Array of Ext.data.Model objects.
31670      * - operation : The Ext.data.Operation itself.
31671      * - success : True when operation completed successfully.
31672      */
31673     callback: undefined,
31674
31675     /**
31676      * @cfg {Object} scope
31677      * Scope for the {@link #callback} function.
31678      */
31679     scope: undefined,
31680
31681     /**
31682      * @property {Boolean} started
31683      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
31684      * @private
31685      */
31686     started: false,
31687
31688     /**
31689      * @property {Boolean} running
31690      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
31691      * @private
31692      */
31693     running: false,
31694
31695     /**
31696      * @property {Boolean} complete
31697      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
31698      * @private
31699      */
31700     complete: false,
31701
31702     /**
31703      * @property {Boolean} success
31704      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
31705      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
31706      * {@link #wasSuccessful} to query success status.
31707      * @private
31708      */
31709     success: undefined,
31710
31711     /**
31712      * @property {Boolean} exception
31713      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
31714      * @private
31715      */
31716     exception: false,
31717
31718     /**
31719      * @property {String/Object} error
31720      * The error object passed when {@link #setException} was called. This could be any object or primitive.
31721      * @private
31722      */
31723     error: undefined,
31724
31725     /**
31726      * @property {RegExp} actionCommitRecordsRe
31727      * The RegExp used to categorize actions that require record commits.
31728      */
31729     actionCommitRecordsRe: /^(?:create|update)$/i,
31730
31731     /**
31732      * @property {RegExp} actionSkipSyncRe
31733      * The RegExp used to categorize actions that skip local record synchronization. This defaults
31734      * to match 'destroy'.
31735      */
31736     actionSkipSyncRe: /^destroy$/i,
31737
31738     /**
31739      * Creates new Operation object.
31740      * @param {Object} config (optional) Config object.
31741      */
31742     constructor: function(config) {
31743         Ext.apply(this, config || {});
31744     },
31745
31746     /**
31747      * This method is called to commit data to this instance's records given the records in
31748      * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
31749      * those records (for 'create' and 'update' actions).
31750      *
31751      * If this {@link #action} is 'destroy', any server records are ignored and the
31752      * {@link Ext.data.Model#commit} method is not called.
31753      *
31754      * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
31755      * the server.
31756      * @markdown
31757      */
31758     commitRecords: function (serverRecords) {
31759         var me = this,
31760             mc, index, clientRecords, serverRec, clientRec;
31761
31762         if (!me.actionSkipSyncRe.test(me.action)) {
31763             clientRecords = me.records;
31764
31765             if (clientRecords && clientRecords.length) {
31766                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
31767                 mc.addAll(clientRecords);
31768
31769                 for (index = serverRecords ? serverRecords.length : 0; index--; ) {
31770                     serverRec = serverRecords[index];
31771                     clientRec = mc.get(serverRec.getId());
31772
31773                     if (clientRec) {
31774                         clientRec.beginEdit();
31775                         clientRec.set(serverRec.data);
31776                         clientRec.endEdit(true);
31777                     }
31778                 }
31779
31780                 if (me.actionCommitRecordsRe.test(me.action)) {
31781                     for (index = clientRecords.length; index--; ) {
31782                         clientRecords[index].commit();
31783                     }
31784                 }
31785             }
31786         }
31787     },
31788
31789     /**
31790      * Marks the Operation as started.
31791      */
31792     setStarted: function() {
31793         this.started = true;
31794         this.running = true;
31795     },
31796
31797     /**
31798      * Marks the Operation as completed.
31799      */
31800     setCompleted: function() {
31801         this.complete = true;
31802         this.running  = false;
31803     },
31804
31805     /**
31806      * Marks the Operation as successful.
31807      */
31808     setSuccessful: function() {
31809         this.success = true;
31810     },
31811
31812     /**
31813      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
31814      * @param {String/Object} error (optional) error string/object
31815      */
31816     setException: function(error) {
31817         this.exception = true;
31818         this.success = false;
31819         this.running = false;
31820         this.error = error;
31821     },
31822
31823     /**
31824      * Returns true if this Operation encountered an exception (see also {@link #getError})
31825      * @return {Boolean} True if there was an exception
31826      */
31827     hasException: function() {
31828         return this.exception === true;
31829     },
31830
31831     /**
31832      * Returns the error string or object that was set using {@link #setException}
31833      * @return {String/Object} The error object
31834      */
31835     getError: function() {
31836         return this.error;
31837     },
31838
31839     /**
31840      * Returns an array of Ext.data.Model instances as set by the Proxy.
31841      * @return {Ext.data.Model[]} Any loaded Records
31842      */
31843     getRecords: function() {
31844         var resultSet = this.getResultSet();
31845
31846         return (resultSet === undefined ? this.records : resultSet.records);
31847     },
31848
31849     /**
31850      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
31851      * instances as well as meta data such as number of instances fetched, number available etc
31852      * @return {Ext.data.ResultSet} The ResultSet object
31853      */
31854     getResultSet: function() {
31855         return this.resultSet;
31856     },
31857
31858     /**
31859      * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
31860      * {@link #isRunning} to test if the Operation is currently running.
31861      * @return {Boolean} True if the Operation has started
31862      */
31863     isStarted: function() {
31864         return this.started === true;
31865     },
31866
31867     /**
31868      * Returns true if the Operation has been started but has not yet completed.
31869      * @return {Boolean} True if the Operation is currently running
31870      */
31871     isRunning: function() {
31872         return this.running === true;
31873     },
31874
31875     /**
31876      * Returns true if the Operation has been completed
31877      * @return {Boolean} True if the Operation is complete
31878      */
31879     isComplete: function() {
31880         return this.complete === true;
31881     },
31882
31883     /**
31884      * Returns true if the Operation has completed and was successful
31885      * @return {Boolean} True if successful
31886      */
31887     wasSuccessful: function() {
31888         return this.isComplete() && this.success === true;
31889     },
31890
31891     /**
31892      * @private
31893      * Associates this Operation with a Batch
31894      * @param {Ext.data.Batch} batch The batch
31895      */
31896     setBatch: function(batch) {
31897         this.batch = batch;
31898     },
31899
31900     /**
31901      * Checks whether this operation should cause writing to occur.
31902      * @return {Boolean} Whether the operation should cause a write to occur.
31903      */
31904     allowWrite: function() {
31905         return this.action != 'read';
31906     }
31907 });
31908 /**
31909  * @author Ed Spencer
31910  *
31911  * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
31912  * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
31913  */
31914 Ext.define('Ext.data.validations', {
31915     singleton: true,
31916     
31917     /**
31918      * @property {String} presenceMessage
31919      * The default error message used when a presence validation fails.
31920      */
31921     presenceMessage: 'must be present',
31922     
31923     /**
31924      * @property {String} lengthMessage
31925      * The default error message used when a length validation fails.
31926      */
31927     lengthMessage: 'is the wrong length',
31928     
31929     /**
31930      * @property {Boolean} formatMessage
31931      * The default error message used when a format validation fails.
31932      */
31933     formatMessage: 'is the wrong format',
31934     
31935     /**
31936      * @property {String} inclusionMessage
31937      * The default error message used when an inclusion validation fails.
31938      */
31939     inclusionMessage: 'is not included in the list of acceptable values',
31940     
31941     /**
31942      * @property {String} exclusionMessage
31943      * The default error message used when an exclusion validation fails.
31944      */
31945     exclusionMessage: 'is not an acceptable value',
31946     
31947     /**
31948      * @property {String} emailMessage
31949      * The default error message used when an email validation fails
31950      */
31951     emailMessage: 'is not a valid email address',
31952     
31953     /**
31954      * @property {RegExp} emailRe
31955      * The regular expression used to validate email addresses
31956      */
31957     emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
31958     
31959     /**
31960      * Validates that the given value is present.
31961      * For example:
31962      *
31963      *     validations: [{type: 'presence', field: 'age'}]
31964      *
31965      * @param {Object} config Config object
31966      * @param {Object} value The value to validate
31967      * @return {Boolean} True if validation passed
31968      */
31969     presence: function(config, value) {
31970         if (value === undefined) {
31971             value = config;
31972         }
31973         
31974         //we need an additional check for zero here because zero is an acceptable form of present data
31975         return !!value || value === 0;
31976     },
31977     
31978     /**
31979      * Returns true if the given value is between the configured min and max values.
31980      * For example:
31981      *
31982      *     validations: [{type: 'length', field: 'name', min: 2}]
31983      *
31984      * @param {Object} config Config object
31985      * @param {String} value The value to validate
31986      * @return {Boolean} True if the value passes validation
31987      */
31988     length: function(config, value) {
31989         if (value === undefined || value === null) {
31990             return false;
31991         }
31992         
31993         var length = value.length,
31994             min    = config.min,
31995             max    = config.max;
31996         
31997         if ((min && length < min) || (max && length > max)) {
31998             return false;
31999         } else {
32000             return true;
32001         }
32002     },
32003     
32004     /**
32005      * Validates that an email string is in the correct format
32006      * @param {Object} config Config object
32007      * @param {String} email The email address
32008      * @return {Boolean} True if the value passes validation
32009      */
32010     email: function(config, email) {
32011         return Ext.data.validations.emailRe.test(email);
32012     },
32013     
32014     /**
32015      * Returns true if the given value passes validation against the configured `matcher` regex.
32016      * For example:
32017      *
32018      *     validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
32019      *
32020      * @param {Object} config Config object
32021      * @param {String} value The value to validate
32022      * @return {Boolean} True if the value passes the format validation
32023      */
32024     format: function(config, value) {
32025         return !!(config.matcher && config.matcher.test(value));
32026     },
32027     
32028     /**
32029      * Validates that the given value is present in the configured `list`.
32030      * For example:
32031      *
32032      *     validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
32033      *
32034      * @param {Object} config Config object
32035      * @param {String} value The value to validate
32036      * @return {Boolean} True if the value is present in the list
32037      */
32038     inclusion: function(config, value) {
32039         return config.list && Ext.Array.indexOf(config.list,value) != -1;
32040     },
32041     
32042     /**
32043      * Validates that the given value is present in the configured `list`.
32044      * For example:
32045      *
32046      *     validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
32047      *
32048      * @param {Object} config Config object
32049      * @param {String} value The value to validate
32050      * @return {Boolean} True if the value is not present in the list
32051      */
32052     exclusion: function(config, value) {
32053         return config.list && Ext.Array.indexOf(config.list,value) == -1;
32054     }
32055 });
32056 /**
32057  * @author Ed Spencer
32058  *
32059  * Simple wrapper class that represents a set of records returned by a Proxy.
32060  */
32061 Ext.define('Ext.data.ResultSet', {
32062     /**
32063      * @cfg {Boolean} loaded
32064      * True if the records have already been loaded. This is only meaningful when dealing with
32065      * SQL-backed proxies.
32066      */
32067     loaded: true,
32068
32069     /**
32070      * @cfg {Number} count
32071      * The number of records in this ResultSet. Note that total may differ from this number.
32072      */
32073     count: 0,
32074
32075     /**
32076      * @cfg {Number} total
32077      * The total number of records reported by the data source. This ResultSet may form a subset of
32078      * those records (see {@link #count}).
32079      */
32080     total: 0,
32081
32082     /**
32083      * @cfg {Boolean} success
32084      * True if the ResultSet loaded successfully, false if any errors were encountered.
32085      */
32086     success: false,
32087
32088     /**
32089      * @cfg {Ext.data.Model[]} records (required)
32090      * The array of record instances.
32091      */
32092
32093     /**
32094      * Creates the resultSet
32095      * @param {Object} [config] Config object.
32096      */
32097     constructor: function(config) {
32098         Ext.apply(this, config);
32099
32100         /**
32101          * @property {Number} totalRecords
32102          * Copy of this.total.
32103          * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
32104          */
32105         this.totalRecords = this.total;
32106
32107         if (config.count === undefined) {
32108             this.count = this.records.length;
32109         }
32110     }
32111 });
32112 /**
32113  * @author Ed Spencer
32114  *
32115  * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
32116  * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
32117  * the Operations.
32118  *
32119  * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
32120  * the config options passed to the JsonWriter's constructor.
32121  *
32122  * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
32123  * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
32124  * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
32125  */
32126 Ext.define('Ext.data.writer.Writer', {
32127     alias: 'writer.base',
32128     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
32129     
32130     /**
32131      * @cfg {Boolean} writeAllFields
32132      * True to write all fields from the record to the server. If set to false it will only send the fields that were
32133      * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
32134      */
32135     writeAllFields: true,
32136     
32137     /**
32138      * @cfg {String} nameProperty
32139      * This property is used to read the key for each value that will be sent to the server. For example:
32140      *
32141      *     Ext.define('Person', {
32142      *         extend: 'Ext.data.Model',
32143      *         fields: [{
32144      *             name: 'first',
32145      *             mapping: 'firstName'
32146      *         }, {
32147      *             name: 'last',
32148      *             mapping: 'lastName'
32149      *         }, {
32150      *             name: 'age'
32151      *         }]
32152      *     });
32153      *     new Ext.data.writer.Writer({
32154      *         writeAllFields: true,
32155      *         nameProperty: 'mapping'
32156      *     });
32157      *
32158      *     // This will be sent to the server
32159      *     {
32160      *         firstName: 'first name value',
32161      *         lastName: 'last name value',
32162      *         age: 1
32163      *     }
32164      *
32165      * If the value is not present, the field name will always be used.
32166      */
32167     nameProperty: 'name',
32168
32169     /**
32170      * Creates new Writer.
32171      * @param {Object} [config] Config object.
32172      */
32173     constructor: function(config) {
32174         Ext.apply(this, config);
32175     },
32176
32177     /**
32178      * Prepares a Proxy's Ext.data.Request object
32179      * @param {Ext.data.Request} request The request object
32180      * @return {Ext.data.Request} The modified request object
32181      */
32182     write: function(request) {
32183         var operation = request.operation,
32184             records   = operation.records || [],
32185             len       = records.length,
32186             i         = 0,
32187             data      = [];
32188
32189         for (; i < len; i++) {
32190             data.push(this.getRecordData(records[i]));
32191         }
32192         return this.writeRecords(request, data);
32193     },
32194
32195     /**
32196      * Formats the data for each record before sending it to the server. This method should be overridden to format the
32197      * data in a way that differs from the default.
32198      * @param {Object} record The record that we are writing to the server.
32199      * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
32200      * the data property on the record.
32201      */
32202     getRecordData: function(record) {
32203         var isPhantom = record.phantom === true,
32204             writeAll = this.writeAllFields || isPhantom,
32205             nameProperty = this.nameProperty,
32206             fields = record.fields,
32207             data = {},
32208             changes,
32209             name,
32210             field,
32211             key;
32212         
32213         if (writeAll) {
32214             fields.each(function(field){
32215                 if (field.persist) {
32216                     name = field[nameProperty] || field.name;
32217                     data[name] = record.get(field.name);
32218                 }
32219             });
32220         } else {
32221             // Only write the changes
32222             changes = record.getChanges();
32223             for (key in changes) {
32224                 if (changes.hasOwnProperty(key)) {
32225                     field = fields.get(key);
32226                     name = field[nameProperty] || field.name;
32227                     data[name] = changes[key];
32228                 }
32229             }
32230             if (!isPhantom) {
32231                 // always include the id for non phantoms
32232                 data[record.idProperty] = record.getId();
32233             }
32234         }
32235         return data;
32236     }
32237 });
32238
32239 /**
32240  * A mixin to add floating capability to a Component.
32241  */
32242 Ext.define('Ext.util.Floating', {
32243
32244     uses: ['Ext.Layer', 'Ext.window.Window'],
32245
32246     /**
32247      * @cfg {Boolean} focusOnToFront
32248      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
32249      * it is {@link #toFront brought to the front}.
32250      */
32251     focusOnToFront: true,
32252
32253     /**
32254      * @cfg {String/Boolean} shadow
32255      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
32256      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
32257      * shadow.
32258      */
32259     shadow: 'sides',
32260
32261     constructor: function(config) {
32262         var me = this;
32263         
32264         me.floating = true;
32265         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
32266             hideMode: me.hideMode,
32267             hidden: me.hidden,
32268             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
32269             shadowOffset: me.shadowOffset,
32270             constrain: false,
32271             shim: me.shim === false ? false : undefined
32272         }), me.el);
32273     },
32274
32275     onFloatRender: function() {
32276         var me = this;
32277         me.zIndexParent = me.getZIndexParent();
32278         me.setFloatParent(me.ownerCt);
32279         delete me.ownerCt;
32280
32281         if (me.zIndexParent) {
32282             me.zIndexParent.registerFloatingItem(me);
32283         } else {
32284             Ext.WindowManager.register(me);
32285         }
32286     },
32287
32288     setFloatParent: function(floatParent) {
32289         var me = this;
32290
32291         // Remove listeners from previous floatParent
32292         if (me.floatParent) {
32293             me.mun(me.floatParent, {
32294                 hide: me.onFloatParentHide,
32295                 show: me.onFloatParentShow,
32296                 scope: me
32297             });
32298         }
32299
32300         me.floatParent = floatParent;
32301
32302         // Floating Components as children of Containers must hide when their parent hides.
32303         if (floatParent) {
32304             me.mon(me.floatParent, {
32305                 hide: me.onFloatParentHide,
32306                 show: me.onFloatParentShow,
32307                 scope: me
32308             });
32309         }
32310
32311         // If a floating Component is configured to be constrained, but has no configured
32312         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
32313         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
32314             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
32315         }
32316     },
32317
32318     onFloatParentHide: function() {
32319         var me = this;
32320         
32321         if (me.hideOnParentHide !== false) {
32322             me.showOnParentShow = me.isVisible();
32323             me.hide();
32324         }
32325     },
32326
32327     onFloatParentShow: function() {
32328         if (this.showOnParentShow) {
32329             delete this.showOnParentShow;
32330             this.show();
32331         }
32332     },
32333
32334     /**
32335      * @private
32336      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
32337      *
32338      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
32339      *
32340      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
32341      * and the global Ext.WindowManager will be used.
32342      */
32343     getZIndexParent: function() {
32344         var p = this.ownerCt,
32345             c;
32346
32347         if (p) {
32348             while (p) {
32349                 c = p;
32350                 p = p.ownerCt;
32351             }
32352             if (c.floating) {
32353                 return c;
32354             }
32355         }
32356     },
32357
32358     // private
32359     // z-index is managed by the zIndexManager and may be overwritten at any time.
32360     // Returns the next z-index to be used.
32361     // If this is a Container, then it will have rebased any managed floating Components,
32362     // and so the next available z-index will be approximately 10000 above that.
32363     setZIndex: function(index) {
32364         var me = this;
32365         me.el.setZIndex(index);
32366
32367         // Next item goes 10 above;
32368         index += 10;
32369
32370         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
32371         // The returned value is a round number approximately 10000 above the last z-index used.
32372         if (me.floatingItems) {
32373             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
32374         }
32375         return index;
32376     },
32377
32378     /**
32379      * Moves this floating Component into a constrain region.
32380      *
32381      * By default, this Component is constrained to be within the container it was added to, or the element it was
32382      * rendered to.
32383      *
32384      * An alternative constraint may be passed.
32385      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
32386      * to be constrained. Defaults to the element into which this floating Component was rendered.
32387      */
32388     doConstrain: function(constrainTo) {
32389         var me = this,
32390             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
32391             xy;
32392
32393         if (vector) {
32394             xy = me.getPosition();
32395             xy[0] += vector[0];
32396             xy[1] += vector[1];
32397             me.setPosition(xy);
32398         }
32399     },
32400
32401
32402     /**
32403      * Gets the x/y offsets to constrain this float
32404      * @private
32405      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
32406      * @return {Number[]} The x/y constraints
32407      */
32408     getConstrainVector: function(constrainTo){
32409         var me = this,
32410             el;
32411
32412         if (me.constrain || me.constrainHeader) {
32413             el = me.constrainHeader ? me.header.el : me.el;
32414             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
32415             return el.getConstrainVector(constrainTo);
32416         }
32417     },
32418
32419     /**
32420      * Aligns this floating Component to the specified element
32421      *
32422      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
32423      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
32424      * omponent instance. If a string id is passed, it will be used as an element id.
32425      * @param {String} [position="tl-bl?"] The position to align to (see {@link
32426      * Ext.Element#alignTo} for more details).
32427      * @param {Number[]} [offsets] Offset the positioning by [x, y]
32428      * @return {Ext.Component} this
32429      */
32430     alignTo: function(element, position, offsets) {
32431         if (element.isComponent) {
32432             element = element.getEl();
32433         }
32434         var xy = this.el.getAlignToXY(element, position, offsets);
32435         this.setPagePosition(xy);
32436         return this;
32437     },
32438
32439     /**
32440      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
32441      * Ext.ZIndexManager ZIndexManager}
32442      *
32443      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
32444      *
32445      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
32446      * @return {Ext.Component} this
32447      */
32448     toFront: function(preventFocus) {
32449         var me = this;
32450
32451         // Find the floating Component which provides the base for this Component's zIndexing.
32452         // That must move to front to then be able to rebase its zIndex stack and move this to the front
32453         if (me.zIndexParent) {
32454             me.zIndexParent.toFront(true);
32455         }
32456         if (me.zIndexManager.bringToFront(me)) {
32457             if (!Ext.isDefined(preventFocus)) {
32458                 preventFocus = !me.focusOnToFront;
32459             }
32460             if (!preventFocus) {
32461                 // Kick off a delayed focus request.
32462                 // If another floating Component is toFronted before the delay expires
32463                 // this will not receive focus.
32464                 me.focus(false, true);
32465             }
32466         }
32467         return me;
32468     },
32469
32470     /**
32471      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
32472      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
32473      *
32474      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
32475      *
32476      * This method also fires the {@link Ext.Component#activate activate} or
32477      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
32478      *
32479      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
32480      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
32481      */
32482     setActive: function(active, newActive) {
32483         var me = this;
32484         
32485         if (active) {
32486             if (me.el.shadow && !me.maximized) {
32487                 me.el.enableShadow(true);
32488             }
32489             me.fireEvent('activate', me);
32490         } else {
32491             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
32492             // can keep their shadows all the time
32493             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
32494                 me.el.disableShadow();
32495             }
32496             me.fireEvent('deactivate', me);
32497         }
32498     },
32499
32500     /**
32501      * Sends this Component to the back of (lower z-index than) any other visible windows
32502      * @return {Ext.Component} this
32503      */
32504     toBack: function() {
32505         this.zIndexManager.sendToBack(this);
32506         return this;
32507     },
32508
32509     /**
32510      * Center this Component in its container.
32511      * @return {Ext.Component} this
32512      */
32513     center: function() {
32514         var me = this,
32515             xy = me.el.getAlignToXY(me.container, 'c-c');
32516         me.setPagePosition(xy);
32517         return me;
32518     },
32519
32520     // private
32521     syncShadow : function(){
32522         if (this.floating) {
32523             this.el.sync(true);
32524         }
32525     },
32526
32527     // private
32528     fitContainer: function() {
32529         var parent = this.floatParent,
32530             container = parent ? parent.getTargetEl() : this.container,
32531             size = container.getViewSize(false);
32532
32533         this.setSize(size);
32534     }
32535 });
32536 /**
32537  * Base Layout class - extended by ComponentLayout and ContainerLayout
32538  */
32539 Ext.define('Ext.layout.Layout', {
32540
32541     /* Begin Definitions */
32542
32543     /* End Definitions */
32544
32545     isLayout: true,
32546     initialized: false,
32547
32548     statics: {
32549         create: function(layout, defaultType) {
32550             var type;
32551             if (layout instanceof Ext.layout.Layout) {
32552                 return Ext.createByAlias('layout.' + layout);
32553             } else {
32554                 if (!layout || typeof layout === 'string') {
32555                     type = layout || defaultType;
32556                     layout = {};                    
32557                 }
32558                 else {
32559                     type = layout.type || defaultType;
32560                 }
32561                 return Ext.createByAlias('layout.' + type, layout || {});
32562             }
32563         }
32564     },
32565
32566     constructor : function(config) {
32567         this.id = Ext.id(null, this.type + '-');
32568         Ext.apply(this, config);
32569     },
32570
32571     /**
32572      * @private
32573      */
32574     layout : function() {
32575         var me = this;
32576         me.layoutBusy = true;
32577         me.initLayout();
32578
32579         if (me.beforeLayout.apply(me, arguments) !== false) {
32580             me.layoutCancelled = false;
32581             me.onLayout.apply(me, arguments);
32582             me.childrenChanged = false;
32583             me.owner.needsLayout = false;
32584             me.layoutBusy = false;
32585             me.afterLayout.apply(me, arguments);
32586         }
32587         else {
32588             me.layoutCancelled = true;
32589         }
32590         me.layoutBusy = false;
32591         me.doOwnerCtLayouts();
32592     },
32593
32594     beforeLayout : function() {
32595         this.renderChildren();
32596         return true;
32597     },
32598
32599     renderChildren: function () {
32600         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
32601     },
32602
32603     /**
32604      * @private
32605      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
32606      * also determines if the items are in the proper place dom.
32607      */
32608     renderItems : function(items, target) {
32609         var me = this,
32610             ln = items.length,
32611             i = 0,
32612             item;
32613
32614         for (; i < ln; i++) {
32615             item = items[i];
32616             if (item && !item.rendered) {
32617                 me.renderItem(item, target, i);
32618             } else if (!me.isValidParent(item, target, i)) {
32619                 me.moveItem(item, target, i);
32620             } else {
32621                 // still need to configure the item, it may have moved in the container.
32622                 me.configureItem(item);
32623             }
32624         }
32625     },
32626
32627     // @private - Validates item is in the proper place in the dom.
32628     isValidParent : function(item, target, position) {
32629         var dom = item.el ? item.el.dom : Ext.getDom(item);
32630         if (dom && target && target.dom) {
32631             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
32632                 return false;
32633             }
32634             return (dom.parentNode == (target.dom || target));
32635         }
32636         return false;
32637     },
32638
32639     /**
32640      * @private
32641      * Renders the given Component into the target Element.
32642      * @param {Ext.Component} item The Component to render
32643      * @param {Ext.Element} target The target Element
32644      * @param {Number} position The position within the target to render the item to
32645      */
32646     renderItem : function(item, target, position) {
32647         var me = this;
32648         if (!item.rendered) {
32649             if (me.itemCls) {
32650                 item.addCls(me.itemCls);
32651             }
32652             if (me.owner.itemCls) {
32653                 item.addCls(me.owner.itemCls);
32654             }
32655             item.render(target, position);
32656             me.configureItem(item);
32657             me.childrenChanged = true;
32658         }
32659     },
32660
32661     /**
32662      * @private
32663      * Moved Component to the provided target instead.
32664      */
32665     moveItem : function(item, target, position) {
32666         // Make sure target is a dom element
32667         target = target.dom || target;
32668         if (typeof position == 'number') {
32669             position = target.childNodes[position];
32670         }
32671         target.insertBefore(item.el.dom, position || null);
32672         item.container = Ext.get(target);
32673         this.configureItem(item);
32674         this.childrenChanged = true;
32675     },
32676
32677     /**
32678      * @private
32679      * Adds the layout's targetCls if necessary and sets
32680      * initialized flag when complete.
32681      */
32682     initLayout : function() {
32683         var me = this,
32684             targetCls = me.targetCls;
32685             
32686         if (!me.initialized && !Ext.isEmpty(targetCls)) {
32687             me.getTarget().addCls(targetCls);
32688         }
32689         me.initialized = true;
32690     },
32691
32692     // @private Sets the layout owner
32693     setOwner : function(owner) {
32694         this.owner = owner;
32695     },
32696
32697     // @private - Returns empty array
32698     getLayoutItems : function() {
32699         return [];
32700     },
32701
32702     /**
32703      * @private
32704      * Applies itemCls
32705      * Empty template method
32706      */
32707     configureItem: Ext.emptyFn,
32708     
32709     // Placeholder empty functions for subclasses to extend
32710     onLayout : Ext.emptyFn,
32711     afterLayout : Ext.emptyFn,
32712     onRemove : Ext.emptyFn,
32713     onDestroy : Ext.emptyFn,
32714     doOwnerCtLayouts : Ext.emptyFn,
32715
32716     /**
32717      * @private
32718      * Removes itemCls
32719      */
32720     afterRemove : function(item) {
32721         var el = item.el,
32722             owner = this.owner,
32723             itemCls = this.itemCls,
32724             ownerCls = owner.itemCls;
32725             
32726         // Clear managed dimensions flag when removed from the layout.
32727         if (item.rendered && !item.isDestroyed) {
32728             if (itemCls) {
32729                 el.removeCls(itemCls);
32730             }
32731             if (ownerCls) {
32732                 el.removeCls(ownerCls);
32733             }
32734         }
32735
32736         // These flags are set at the time a child item is added to a layout.
32737         // The layout must decide if it is managing the item's width, or its height, or both.
32738         // See AbstractComponent for docs on these properties.
32739         delete item.layoutManagedWidth;
32740         delete item.layoutManagedHeight;
32741     },
32742
32743     /**
32744      * Destroys this layout. This is a template method that is empty by default, but should be implemented
32745      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
32746      * @template
32747      */
32748     destroy : function() {
32749         var targetCls = this.targetCls,
32750             target;
32751         
32752         if (!Ext.isEmpty(targetCls)) {
32753             target = this.getTarget();
32754             if (target) {
32755                 target.removeCls(targetCls);
32756             }
32757         }
32758         this.onDestroy();
32759     }
32760 });
32761 /**
32762  * @class Ext.ZIndexManager
32763  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
32764  * and Component activation behavior, including masking below the active (topmost) Component.</p>
32765  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
32766  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
32767  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
32768  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
32769  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
32770  * guaranteed to be correct, relative to the Window.</p>
32771  */
32772 Ext.define('Ext.ZIndexManager', {
32773
32774     alternateClassName: 'Ext.WindowGroup',
32775
32776     statics: {
32777         zBase : 9000
32778     },
32779
32780     constructor: function(container) {
32781         var me = this;
32782
32783         me.list = {};
32784         me.zIndexStack = [];
32785         me.front = null;
32786
32787         if (container) {
32788
32789             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
32790             if (container.isContainer) {
32791                 container.on('resize', me._onContainerResize, me);
32792                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
32793                 // The containing element we will be dealing with (eg masking) is the content target
32794                 me.targetEl = container.getTargetEl();
32795                 me.container = container;
32796             }
32797             // This is the ZIndexManager for a DOM element
32798             else {
32799                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
32800                 me.zseed = me.getNextZSeed();
32801                 me.targetEl = Ext.get(container);
32802             }
32803         }
32804         // No container passed means we are the global WindowManager. Our target is the doc body.
32805         // DOM must be ready to collect that ref.
32806         else {
32807             Ext.EventManager.onWindowResize(me._onContainerResize, me);
32808             me.zseed = me.getNextZSeed();
32809             Ext.onDocumentReady(function() {
32810                 me.targetEl = Ext.getBody();
32811             });
32812         }
32813     },
32814
32815     getNextZSeed: function() {
32816         return (Ext.ZIndexManager.zBase += 10000);
32817     },
32818
32819     setBase: function(baseZIndex) {
32820         this.zseed = baseZIndex;
32821         return this.assignZIndices();
32822     },
32823
32824     // private
32825     assignZIndices: function() {
32826         var a = this.zIndexStack,
32827             len = a.length,
32828             i = 0,
32829             zIndex = this.zseed,
32830             comp;
32831
32832         for (; i < len; i++) {
32833             comp = a[i];
32834             if (comp && !comp.hidden) {
32835
32836                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
32837                 // that Component.
32838                 // If it's just a plain floating Component such as a BoundList, then the
32839                 // return value is the passed value plus 10, ready for the next item.
32840                 // If a floating *Container* has its zIndex set, it re-orders its managed
32841                 // floating children, starting from that new base, and returns a value 10000 above
32842                 // the highest zIndex which it allocates.
32843                 zIndex = comp.setZIndex(zIndex);
32844             }
32845         }
32846         this._activateLast();
32847         return zIndex;
32848     },
32849
32850     // private
32851     _setActiveChild: function(comp) {
32852         if (comp !== this.front) {
32853
32854             if (this.front) {
32855                 this.front.setActive(false, comp);
32856             }
32857             this.front = comp;
32858             if (comp) {
32859                 comp.setActive(true);
32860                 if (comp.modal) {
32861                     this._showModalMask(comp);
32862                 }
32863             }
32864         }
32865     },
32866
32867     // private
32868     _activateLast: function(justHidden) {
32869         var comp,
32870             lastActivated = false,
32871             i;
32872
32873         // Go down through the z-index stack.
32874         // Activate the next visible one down.
32875         // Keep going down to find the next visible modal one to shift the modal mask down under
32876         for (i = this.zIndexStack.length-1; i >= 0; --i) {
32877             comp = this.zIndexStack[i];
32878             if (!comp.hidden) {
32879                 if (!lastActivated) {
32880                     this._setActiveChild(comp);
32881                     lastActivated = true;
32882                 }
32883
32884                 // Move any modal mask down to just under the next modal floater down the stack
32885                 if (comp.modal) {
32886                     this._showModalMask(comp);
32887                     return;
32888                 }
32889             }
32890         }
32891
32892         // none to activate, so there must be no modal mask.
32893         // And clear the currently active property
32894         this._hideModalMask();
32895         if (!lastActivated) {
32896             this._setActiveChild(null);
32897         }
32898     },
32899
32900     _showModalMask: function(comp) {
32901         var zIndex = comp.el.getStyle('zIndex') - 4,
32902             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
32903             parentBox;
32904         
32905         if (!maskTarget) {
32906             Ext.global.console && Ext.global.console.warn && Ext.global.console.warn('mask target could not be found. Mask cannot be shown');
32907             return;
32908         }
32909         
32910         parentBox = maskTarget.getBox();
32911
32912         if (!this.mask) {
32913             this.mask = Ext.getBody().createChild({
32914                 cls: Ext.baseCSSPrefix + 'mask'
32915             });
32916             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
32917             this.mask.on('click', this._onMaskClick, this);
32918         }
32919         if (maskTarget.dom === document.body) {
32920             parentBox.height = Ext.Element.getViewHeight();
32921         }
32922         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
32923         this.mask.setBox(parentBox);
32924         this.mask.setStyle('zIndex', zIndex);
32925         this.mask.show();
32926     },
32927
32928     _hideModalMask: function() {
32929         if (this.mask && this.mask.dom.parentNode) {
32930             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
32931             this.mask.hide();
32932         }
32933     },
32934
32935     _onMaskClick: function() {
32936         if (this.front) {
32937             this.front.focus();
32938         }
32939     },
32940
32941     _onContainerResize: function() {
32942         if (this.mask && this.mask.isVisible()) {
32943             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
32944         }
32945     },
32946
32947     /**
32948      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
32949      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
32950      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
32951      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
32952      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
32953      * ZIndexManager in the desktop sample app:</p><code><pre>
32954 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
32955 </pre></code>
32956      * @param {Ext.Component} comp The Component to register.
32957      */
32958     register : function(comp) {
32959         if (comp.zIndexManager) {
32960             comp.zIndexManager.unregister(comp);
32961         }
32962         comp.zIndexManager = this;
32963
32964         this.list[comp.id] = comp;
32965         this.zIndexStack.push(comp);
32966         comp.on('hide', this._activateLast, this);
32967     },
32968
32969     /**
32970      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
32971      * need to be called. Components are automatically unregistered upon destruction.
32972      * See {@link #register}.</p>
32973      * @param {Ext.Component} comp The Component to unregister.
32974      */
32975     unregister : function(comp) {
32976         delete comp.zIndexManager;
32977         if (this.list && this.list[comp.id]) {
32978             delete this.list[comp.id];
32979             comp.un('hide', this._activateLast);
32980             Ext.Array.remove(this.zIndexStack, comp);
32981
32982             // Destruction requires that the topmost visible floater be activated. Same as hiding.
32983             this._activateLast(comp);
32984         }
32985     },
32986
32987     /**
32988      * Gets a registered Component by id.
32989      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
32990      * @return {Ext.Component}
32991      */
32992     get : function(id) {
32993         return typeof id == "object" ? id : this.list[id];
32994     },
32995
32996    /**
32997      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
32998      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32999      * @return {Boolean} True if the dialog was brought to the front, else false
33000      * if it was already in front
33001      */
33002     bringToFront : function(comp) {
33003         comp = this.get(comp);
33004         if (comp !== this.front) {
33005             Ext.Array.remove(this.zIndexStack, comp);
33006             this.zIndexStack.push(comp);
33007             this.assignZIndices();
33008             return true;
33009         }
33010         if (comp.modal) {
33011             this._showModalMask(comp);
33012         }
33013         return false;
33014     },
33015
33016     /**
33017      * Sends the specified Component to the back of other active Components in this ZIndexManager.
33018      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
33019      * @return {Ext.Component} The Component
33020      */
33021     sendToBack : function(comp) {
33022         comp = this.get(comp);
33023         Ext.Array.remove(this.zIndexStack, comp);
33024         this.zIndexStack.unshift(comp);
33025         this.assignZIndices();
33026         return comp;
33027     },
33028
33029     /**
33030      * Hides all Components managed by this ZIndexManager.
33031      */
33032     hideAll : function() {
33033         for (var id in this.list) {
33034             if (this.list[id].isComponent && this.list[id].isVisible()) {
33035                 this.list[id].hide();
33036             }
33037         }
33038     },
33039
33040     /**
33041      * @private
33042      * Temporarily hides all currently visible managed Components. This is for when
33043      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
33044      * they should all be hidden just for the duration of the drag.
33045      */
33046     hide: function() {
33047         var i = 0,
33048             ln = this.zIndexStack.length,
33049             comp;
33050
33051         this.tempHidden = [];
33052         for (; i < ln; i++) {
33053             comp = this.zIndexStack[i];
33054             if (comp.isVisible()) {
33055                 this.tempHidden.push(comp);
33056                 comp.hide();
33057             }
33058         }
33059     },
33060
33061     /**
33062      * @private
33063      * Restores temporarily hidden managed Components to visibility.
33064      */
33065     show: function() {
33066         var i = 0,
33067             ln = this.tempHidden.length,
33068             comp,
33069             x,
33070             y;
33071
33072         for (; i < ln; i++) {
33073             comp = this.tempHidden[i];
33074             x = comp.x;
33075             y = comp.y;
33076             comp.show();
33077             comp.setPosition(x, y);
33078         }
33079         delete this.tempHidden;
33080     },
33081
33082     /**
33083      * Gets the currently-active Component in this ZIndexManager.
33084      * @return {Ext.Component} The active Component
33085      */
33086     getActive : function() {
33087         return this.front;
33088     },
33089
33090     /**
33091      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
33092      * The function should accept a single {@link Ext.Component} reference as its only argument and should
33093      * return true if the Component matches the search criteria, otherwise it should return false.
33094      * @param {Function} fn The search function
33095      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
33096      * that gets passed to the function if not specified)
33097      * @return {Array} An array of zero or more matching windows
33098      */
33099     getBy : function(fn, scope) {
33100         var r = [],
33101             i = 0,
33102             len = this.zIndexStack.length,
33103             comp;
33104
33105         for (; i < len; i++) {
33106             comp = this.zIndexStack[i];
33107             if (fn.call(scope||comp, comp) !== false) {
33108                 r.push(comp);
33109             }
33110         }
33111         return r;
33112     },
33113
33114     /**
33115      * Executes the specified function once for every Component in this ZIndexManager, passing each
33116      * Component as the only parameter. Returning false from the function will stop the iteration.
33117      * @param {Function} fn The function to execute for each item
33118      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
33119      */
33120     each : function(fn, scope) {
33121         var comp;
33122         for (var id in this.list) {
33123             comp = this.list[id];
33124             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
33125                 return;
33126             }
33127         }
33128     },
33129
33130     /**
33131      * Executes the specified function once for every Component in this ZIndexManager, passing each
33132      * Component as the only parameter. Returning false from the function will stop the iteration.
33133      * The components are passed to the function starting at the bottom and proceeding to the top.
33134      * @param {Function} fn The function to execute for each item
33135      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
33136      * is executed. Defaults to the current Component in the iteration.
33137      */
33138     eachBottomUp: function (fn, scope) {
33139         var comp,
33140             stack = this.zIndexStack,
33141             i, n;
33142
33143         for (i = 0, n = stack.length ; i < n; i++) {
33144             comp = stack[i];
33145             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
33146                 return;
33147             }
33148         }
33149     },
33150
33151     /**
33152      * Executes the specified function once for every Component in this ZIndexManager, passing each
33153      * Component as the only parameter. Returning false from the function will stop the iteration.
33154      * The components are passed to the function starting at the top and proceeding to the bottom.
33155      * @param {Function} fn The function to execute for each item
33156      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
33157      * is executed. Defaults to the current Component in the iteration.
33158      */
33159     eachTopDown: function (fn, scope) {
33160         var comp,
33161             stack = this.zIndexStack,
33162             i;
33163
33164         for (i = stack.length ; i-- > 0; ) {
33165             comp = stack[i];
33166             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
33167                 return;
33168             }
33169         }
33170     },
33171
33172     destroy: function() {
33173         this.each(function(c) {
33174             c.destroy();
33175         });
33176         delete this.zIndexStack;
33177         delete this.list;
33178         delete this.container;
33179         delete this.targetEl;
33180     }
33181 }, function() {
33182     /**
33183      * @class Ext.WindowManager
33184      * @extends Ext.ZIndexManager
33185      * <p>The default global floating Component group that is available automatically.</p>
33186      * <p>This manages instances of floating Components which were rendered programatically without
33187      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
33188      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
33189      * there are managed by that ZIndexManager.</p>
33190      * @singleton
33191      */
33192     Ext.WindowManager = Ext.WindowMgr = new this();
33193 });
33194
33195 /**
33196  * @private
33197  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
33198  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
33199  * for its container.
33200  */
33201 Ext.define('Ext.layout.container.boxOverflow.None', {
33202     
33203     alternateClassName: 'Ext.layout.boxOverflow.None',
33204     
33205     constructor: function(layout, config) {
33206         this.layout = layout;
33207         Ext.apply(this, config || {});
33208     },
33209
33210     handleOverflow: Ext.emptyFn,
33211
33212     clearOverflow: Ext.emptyFn,
33213     
33214     onRemove: Ext.emptyFn,
33215
33216     /**
33217      * @private
33218      * Normalizes an item reference, string id or numerical index into a reference to the item
33219      * @param {Ext.Component/String/Number} item The item reference, id or index
33220      * @return {Ext.Component} The item
33221      */
33222     getItem: function(item) {
33223         return this.layout.owner.getComponent(item);
33224     },
33225     
33226     onRemove: Ext.emptyFn
33227 });
33228 /**
33229  * @class Ext.util.KeyMap
33230  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
33231  * The constructor accepts the same config object as defined by {@link #addBinding}.
33232  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
33233  * combination it will call the function with this signature (if the match is a multi-key
33234  * combination the callback will still be called only once): (String key, Ext.EventObject e)
33235  * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
33236  * Usage:
33237  <pre><code>
33238 // map one key by key code
33239 var map = new Ext.util.KeyMap("my-element", {
33240     key: 13, // or Ext.EventObject.ENTER
33241     fn: myHandler,
33242     scope: myObject
33243 });
33244
33245 // map multiple keys to one action by string
33246 var map = new Ext.util.KeyMap("my-element", {
33247     key: "a\r\n\t",
33248     fn: myHandler,
33249     scope: myObject
33250 });
33251
33252 // map multiple keys to multiple actions by strings and array of codes
33253 var map = new Ext.util.KeyMap("my-element", [
33254     {
33255         key: [10,13],
33256         fn: function(){ alert("Return was pressed"); }
33257     }, {
33258         key: "abc",
33259         fn: function(){ alert('a, b or c was pressed'); }
33260     }, {
33261         key: "\t",
33262         ctrl:true,
33263         shift:true,
33264         fn: function(){ alert('Control + shift + tab was pressed.'); }
33265     }
33266 ]);
33267 </code></pre>
33268  */
33269 Ext.define('Ext.util.KeyMap', {
33270     alternateClassName: 'Ext.KeyMap',
33271
33272     /**
33273      * Creates new KeyMap.
33274      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
33275      * @param {Object} binding The binding (see {@link #addBinding})
33276      * @param {String} [eventName="keydown"] The event to bind to
33277      */
33278     constructor: function(el, binding, eventName){
33279         var me = this;
33280
33281         Ext.apply(me, {
33282             el: Ext.get(el),
33283             eventName: eventName || me.eventName,
33284             bindings: []
33285         });
33286         if (binding) {
33287             me.addBinding(binding);
33288         }
33289         me.enable();
33290     },
33291
33292     eventName: 'keydown',
33293
33294     /**
33295      * Add a new binding to this KeyMap. The following config object properties are supported:
33296      * <pre>
33297 Property            Type             Description
33298 ----------          ---------------  ----------------------------------------------------------------------
33299 key                 String/Array     A single keycode or an array of keycodes to handle
33300 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)
33301 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)
33302 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)
33303 handler             Function         The function to call when KeyMap finds the expected key combination
33304 fn                  Function         Alias of handler (for backwards-compatibility)
33305 scope               Object           The scope of the callback function
33306 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.
33307 </pre>
33308      *
33309      * Usage:
33310      * <pre><code>
33311 // Create a KeyMap
33312 var map = new Ext.util.KeyMap(document, {
33313     key: Ext.EventObject.ENTER,
33314     fn: handleKey,
33315     scope: this
33316 });
33317
33318 //Add a new binding to the existing KeyMap later
33319 map.addBinding({
33320     key: 'abc',
33321     shift: true,
33322     fn: handleKey,
33323     scope: this
33324 });
33325 </code></pre>
33326      * @param {Object/Object[]} binding A single KeyMap config or an array of configs
33327      */
33328     addBinding : function(binding){
33329         if (Ext.isArray(binding)) {
33330             Ext.each(binding, this.addBinding, this);
33331             return;
33332         }
33333
33334         var keyCode = binding.key,
33335             processed = false,
33336             key,
33337             keys,
33338             keyString,
33339             i,
33340             len;
33341
33342         if (Ext.isString(keyCode)) {
33343             keys = [];
33344             keyString = keyCode.toUpperCase();
33345
33346             for (i = 0, len = keyString.length; i < len; ++i){
33347                 keys.push(keyString.charCodeAt(i));
33348             }
33349             keyCode = keys;
33350             processed = true;
33351         }
33352
33353         if (!Ext.isArray(keyCode)) {
33354             keyCode = [keyCode];
33355         }
33356
33357         if (!processed) {
33358             for (i = 0, len = keyCode.length; i < len; ++i) {
33359                 key = keyCode[i];
33360                 if (Ext.isString(key)) {
33361                     keyCode[i] = key.toUpperCase().charCodeAt(0);
33362                 }
33363             }
33364         }
33365
33366         this.bindings.push(Ext.apply({
33367             keyCode: keyCode
33368         }, binding));
33369     },
33370
33371     /**
33372      * Process any keydown events on the element
33373      * @private
33374      * @param {Ext.EventObject} event
33375      */
33376     handleKeyDown: function(event) {
33377         if (this.enabled) { //just in case
33378             var bindings = this.bindings,
33379                 i = 0,
33380                 len = bindings.length;
33381
33382             event = this.processEvent(event);
33383             for(; i < len; ++i){
33384                 this.processBinding(bindings[i], event);
33385             }
33386         }
33387     },
33388
33389     /**
33390      * Ugly hack to allow this class to be tested. Currently WebKit gives
33391      * no way to raise a key event properly with both
33392      * a) A keycode
33393      * b) The alt/ctrl/shift modifiers
33394      * So we have to simulate them here. Yuk!
33395      * This is a stub method intended to be overridden by tests.
33396      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
33397      * @private
33398      */
33399     processEvent: function(event){
33400         return event;
33401     },
33402
33403     /**
33404      * Process a particular binding and fire the handler if necessary.
33405      * @private
33406      * @param {Object} binding The binding information
33407      * @param {Ext.EventObject} event
33408      */
33409     processBinding: function(binding, event){
33410         if (this.checkModifiers(binding, event)) {
33411             var key = event.getKey(),
33412                 handler = binding.fn || binding.handler,
33413                 scope = binding.scope || this,
33414                 keyCode = binding.keyCode,
33415                 defaultEventAction = binding.defaultEventAction,
33416                 i,
33417                 len,
33418                 keydownEvent = new Ext.EventObjectImpl(event);
33419
33420
33421             for (i = 0, len = keyCode.length; i < len; ++i) {
33422                 if (key === keyCode[i]) {
33423                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
33424                         keydownEvent[defaultEventAction]();
33425                     }
33426                     break;
33427                 }
33428             }
33429         }
33430     },
33431
33432     /**
33433      * Check if the modifiers on the event match those on the binding
33434      * @private
33435      * @param {Object} binding
33436      * @param {Ext.EventObject} event
33437      * @return {Boolean} True if the event matches the binding
33438      */
33439     checkModifiers: function(binding, e){
33440         var keys = ['shift', 'ctrl', 'alt'],
33441             i = 0,
33442             len = keys.length,
33443             val, key;
33444
33445         for (; i < len; ++i){
33446             key = keys[i];
33447             val = binding[key];
33448             if (!(val === undefined || (val === e[key + 'Key']))) {
33449                 return false;
33450             }
33451         }
33452         return true;
33453     },
33454
33455     /**
33456      * Shorthand for adding a single key listener
33457      * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
33458      * following options:
33459      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
33460      * @param {Function} fn The function to call
33461      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
33462      */
33463     on: function(key, fn, scope) {
33464         var keyCode, shift, ctrl, alt;
33465         if (Ext.isObject(key) && !Ext.isArray(key)) {
33466             keyCode = key.key;
33467             shift = key.shift;
33468             ctrl = key.ctrl;
33469             alt = key.alt;
33470         } else {
33471             keyCode = key;
33472         }
33473         this.addBinding({
33474             key: keyCode,
33475             shift: shift,
33476             ctrl: ctrl,
33477             alt: alt,
33478             fn: fn,
33479             scope: scope
33480         });
33481     },
33482
33483     /**
33484      * Returns true if this KeyMap is enabled
33485      * @return {Boolean}
33486      */
33487     isEnabled : function(){
33488         return this.enabled;
33489     },
33490
33491     /**
33492      * Enables this KeyMap
33493      */
33494     enable: function(){
33495         var me = this;
33496         
33497         if (!me.enabled) {
33498             me.el.on(me.eventName, me.handleKeyDown, me);
33499             me.enabled = true;
33500         }
33501     },
33502
33503     /**
33504      * Disable this KeyMap
33505      */
33506     disable: function(){
33507         var me = this;
33508         
33509         if (me.enabled) {
33510             me.el.removeListener(me.eventName, me.handleKeyDown, me);
33511             me.enabled = false;
33512         }
33513     },
33514
33515     /**
33516      * Convenience function for setting disabled/enabled by boolean.
33517      * @param {Boolean} disabled
33518      */
33519     setDisabled : function(disabled){
33520         if (disabled) {
33521             this.disable();
33522         } else {
33523             this.enable();
33524         }
33525     },
33526
33527     /**
33528      * Destroys the KeyMap instance and removes all handlers.
33529      * @param {Boolean} removeEl True to also remove the attached element
33530      */
33531     destroy: function(removeEl){
33532         var me = this;
33533
33534         me.bindings = [];
33535         me.disable();
33536         if (removeEl === true) {
33537             me.el.remove();
33538         }
33539         delete me.el;
33540     }
33541 });
33542 /**
33543  * @class Ext.util.ClickRepeater
33544  * @extends Ext.util.Observable
33545  *
33546  * A wrapper class which can be applied to any element. Fires a "click" event while the
33547  * mouse is pressed. The interval between firings may be specified in the config but
33548  * defaults to 20 milliseconds.
33549  *
33550  * Optionally, a CSS class may be applied to the element during the time it is pressed.
33551  *
33552  */
33553 Ext.define('Ext.util.ClickRepeater', {
33554     extend: 'Ext.util.Observable',
33555
33556     /**
33557      * Creates new ClickRepeater.
33558      * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
33559      * @param {Object} config (optional) Config object.
33560      */
33561     constructor : function(el, config){
33562         this.el = Ext.get(el);
33563         this.el.unselectable();
33564
33565         Ext.apply(this, config);
33566
33567         this.addEvents(
33568         /**
33569          * @event mousedown
33570          * Fires when the mouse button is depressed.
33571          * @param {Ext.util.ClickRepeater} this
33572          * @param {Ext.EventObject} e
33573          */
33574         "mousedown",
33575         /**
33576          * @event click
33577          * Fires on a specified interval during the time the element is pressed.
33578          * @param {Ext.util.ClickRepeater} this
33579          * @param {Ext.EventObject} e
33580          */
33581         "click",
33582         /**
33583          * @event mouseup
33584          * Fires when the mouse key is released.
33585          * @param {Ext.util.ClickRepeater} this
33586          * @param {Ext.EventObject} e
33587          */
33588         "mouseup"
33589         );
33590
33591         if(!this.disabled){
33592             this.disabled = true;
33593             this.enable();
33594         }
33595
33596         // allow inline handler
33597         if(this.handler){
33598             this.on("click", this.handler,  this.scope || this);
33599         }
33600
33601         this.callParent();
33602     },
33603
33604     /**
33605      * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
33606      */
33607
33608     /**
33609      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
33610      */
33611
33612     /**
33613      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
33614      * "interval" and "delay" are ignored.
33615      */
33616
33617     /**
33618      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
33619      */
33620     interval : 20,
33621
33622     /**
33623      * @cfg {Number} delay The initial delay before the repeating event begins firing.
33624      * Similar to an autorepeat key delay.
33625      */
33626     delay: 250,
33627
33628     /**
33629      * @cfg {Boolean} preventDefault True to prevent the default click event
33630      */
33631     preventDefault : true,
33632     /**
33633      * @cfg {Boolean} stopDefault True to stop the default click event
33634      */
33635     stopDefault : false,
33636
33637     timer : 0,
33638
33639     /**
33640      * Enables the repeater and allows events to fire.
33641      */
33642     enable: function(){
33643         if(this.disabled){
33644             this.el.on('mousedown', this.handleMouseDown, this);
33645             if (Ext.isIE){
33646                 this.el.on('dblclick', this.handleDblClick, this);
33647             }
33648             if(this.preventDefault || this.stopDefault){
33649                 this.el.on('click', this.eventOptions, this);
33650             }
33651         }
33652         this.disabled = false;
33653     },
33654
33655     /**
33656      * Disables the repeater and stops events from firing.
33657      */
33658     disable: function(/* private */ force){
33659         if(force || !this.disabled){
33660             clearTimeout(this.timer);
33661             if(this.pressedCls){
33662                 this.el.removeCls(this.pressedCls);
33663             }
33664             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
33665             this.el.removeAllListeners();
33666         }
33667         this.disabled = true;
33668     },
33669
33670     /**
33671      * Convenience function for setting disabled/enabled by boolean.
33672      * @param {Boolean} disabled
33673      */
33674     setDisabled: function(disabled){
33675         this[disabled ? 'disable' : 'enable']();
33676     },
33677
33678     eventOptions: function(e){
33679         if(this.preventDefault){
33680             e.preventDefault();
33681         }
33682         if(this.stopDefault){
33683             e.stopEvent();
33684         }
33685     },
33686
33687     // private
33688     destroy : function() {
33689         this.disable(true);
33690         Ext.destroy(this.el);
33691         this.clearListeners();
33692     },
33693
33694     handleDblClick : function(e){
33695         clearTimeout(this.timer);
33696         this.el.blur();
33697
33698         this.fireEvent("mousedown", this, e);
33699         this.fireEvent("click", this, e);
33700     },
33701
33702     // private
33703     handleMouseDown : function(e){
33704         clearTimeout(this.timer);
33705         this.el.blur();
33706         if(this.pressedCls){
33707             this.el.addCls(this.pressedCls);
33708         }
33709         this.mousedownTime = new Date();
33710
33711         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
33712         this.el.on("mouseout", this.handleMouseOut, this);
33713
33714         this.fireEvent("mousedown", this, e);
33715         this.fireEvent("click", this, e);
33716
33717         // Do not honor delay or interval if acceleration wanted.
33718         if (this.accelerate) {
33719             this.delay = 400;
33720         }
33721
33722         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
33723         // the global shared EventObject gets a new Event put into it before the timer fires.
33724         e = new Ext.EventObjectImpl(e);
33725
33726         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
33727     },
33728
33729     // private
33730     click : function(e){
33731         this.fireEvent("click", this, e);
33732         this.timer =  Ext.defer(this.click, this.accelerate ?
33733             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
33734                 400,
33735                 -390,
33736                 12000) :
33737             this.interval, this, [e]);
33738     },
33739
33740     easeOutExpo : function (t, b, c, d) {
33741         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
33742     },
33743
33744     // private
33745     handleMouseOut : function(){
33746         clearTimeout(this.timer);
33747         if(this.pressedCls){
33748             this.el.removeCls(this.pressedCls);
33749         }
33750         this.el.on("mouseover", this.handleMouseReturn, this);
33751     },
33752
33753     // private
33754     handleMouseReturn : function(){
33755         this.el.un("mouseover", this.handleMouseReturn, this);
33756         if(this.pressedCls){
33757             this.el.addCls(this.pressedCls);
33758         }
33759         this.click();
33760     },
33761
33762     // private
33763     handleMouseUp : function(e){
33764         clearTimeout(this.timer);
33765         this.el.un("mouseover", this.handleMouseReturn, this);
33766         this.el.un("mouseout", this.handleMouseOut, this);
33767         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
33768         if(this.pressedCls){
33769             this.el.removeCls(this.pressedCls);
33770         }
33771         this.fireEvent("mouseup", this, e);
33772     }
33773 });
33774
33775 /**
33776  * @class Ext.layout.component.Component
33777  * @extends Ext.layout.Layout
33778  *
33779  * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
33780  * configuration property.  See {@link Ext.Component#componentLayout} for additional details.
33781  *
33782  * @private
33783  */
33784 Ext.define('Ext.layout.component.Component', {
33785
33786     /* Begin Definitions */
33787
33788     extend: 'Ext.layout.Layout',
33789
33790     /* End Definitions */
33791
33792     type: 'component',
33793
33794     monitorChildren: true,
33795
33796     initLayout : function() {
33797         var me = this,
33798             owner = me.owner,
33799             ownerEl = owner.el;
33800
33801         if (!me.initialized) {
33802             if (owner.frameSize) {
33803                 me.frameSize = owner.frameSize;
33804             }
33805             else {
33806                 owner.frameSize = me.frameSize = {
33807                     top: 0,
33808                     left: 0,
33809                     bottom: 0,
33810                     right: 0
33811                 };
33812             }
33813         }
33814         me.callParent(arguments);
33815     },
33816
33817     beforeLayout : function(width, height, isSetSize, callingContainer) {
33818         this.callParent(arguments);
33819
33820         var me = this,
33821             owner = me.owner,
33822             ownerCt = owner.ownerCt,
33823             layout = owner.layout,
33824             isVisible = owner.isVisible(true),
33825             ownerElChild = owner.el.child,
33826             layoutCollection;
33827
33828         // Cache the size we began with so we can see if there has been any effect.
33829         me.previousComponentSize = me.lastComponentSize;
33830
33831         // Do not allow autoing of any dimensions which are fixed
33832         if (!isSetSize
33833             && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
33834                 (!Ext.isNumber(height) && owner.isFixedHeight()))
33835             // unless we are being told to do so by the ownerCt's layout
33836             && callingContainer && callingContainer !== ownerCt) {
33837             
33838             me.doContainerLayout();
33839             return false;
33840         }
33841
33842         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
33843         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
33844         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
33845             if (owner.hiddenAncestor) {
33846                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
33847                 layoutCollection.remove(owner);
33848                 layoutCollection.add(owner);
33849             }
33850             owner.needsLayout = {
33851                 width: width,
33852                 height: height,
33853                 isSetSize: false
33854             };
33855         }
33856
33857         if (isVisible && this.needsLayout(width, height)) {
33858             return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
33859         }
33860         else {
33861             return false;
33862         }
33863     },
33864
33865     /**
33866     * Check if the new size is different from the current size and only
33867     * trigger a layout if it is necessary.
33868     * @param {Number} width The new width to set.
33869     * @param {Number} height The new height to set.
33870     */
33871     needsLayout : function(width, height) {
33872         var me = this,
33873             widthBeingChanged,
33874             heightBeingChanged;
33875             me.lastComponentSize = me.lastComponentSize || {
33876                 width: -Infinity,
33877                 height: -Infinity
33878             };
33879
33880         // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
33881         widthBeingChanged  = !Ext.isDefined(width)  || me.lastComponentSize.width  !== width;
33882
33883         // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
33884         heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
33885
33886
33887         // isSizing flag added to prevent redundant layouts when going up the layout chain
33888         return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
33889     },
33890
33891     /**
33892     * Set the size of any element supporting undefined, null, and values.
33893     * @param {Number} width The new width to set.
33894     * @param {Number} height The new height to set.
33895     */
33896     setElementSize: function(el, width, height) {
33897         if (width !== undefined && height !== undefined) {
33898             el.setSize(width, height);
33899         }
33900         else if (height !== undefined) {
33901             el.setHeight(height);
33902         }
33903         else if (width !== undefined) {
33904             el.setWidth(width);
33905         }
33906     },
33907
33908     /**
33909      * Returns the owner component's resize element.
33910      * @return {Ext.Element}
33911      */
33912      getTarget : function() {
33913          return this.owner.el;
33914      },
33915
33916     /**
33917      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
33918      * May be overridden in Component layout managers which implement an inner element.
33919      * @return {Ext.Element}
33920      */
33921     getRenderTarget : function() {
33922         return this.owner.el;
33923     },
33924
33925     /**
33926     * Set the size of the target element.
33927     * @param {Number} width The new width to set.
33928     * @param {Number} height The new height to set.
33929     */
33930     setTargetSize : function(width, height) {
33931         var me = this;
33932         me.setElementSize(me.owner.el, width, height);
33933
33934         if (me.owner.frameBody) {
33935             var targetInfo = me.getTargetInfo(),
33936                 padding = targetInfo.padding,
33937                 border = targetInfo.border,
33938                 frameSize = me.frameSize;
33939
33940             me.setElementSize(me.owner.frameBody,
33941                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
33942                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
33943             );
33944         }
33945
33946         me.autoSized = {
33947             width: !Ext.isNumber(width),
33948             height: !Ext.isNumber(height)
33949         };
33950
33951         me.lastComponentSize = {
33952             width: width,
33953             height: height
33954         };
33955     },
33956
33957     getTargetInfo : function() {
33958         if (!this.targetInfo) {
33959             var target = this.getTarget(),
33960                 body = this.owner.getTargetEl();
33961
33962             this.targetInfo = {
33963                 padding: {
33964                     top: target.getPadding('t'),
33965                     right: target.getPadding('r'),
33966                     bottom: target.getPadding('b'),
33967                     left: target.getPadding('l')
33968                 },
33969                 border: {
33970                     top: target.getBorderWidth('t'),
33971                     right: target.getBorderWidth('r'),
33972                     bottom: target.getBorderWidth('b'),
33973                     left: target.getBorderWidth('l')
33974                 },
33975                 bodyMargin: {
33976                     top: body.getMargin('t'),
33977                     right: body.getMargin('r'),
33978                     bottom: body.getMargin('b'),
33979                     left: body.getMargin('l')
33980                 }
33981             };
33982         }
33983         return this.targetInfo;
33984     },
33985
33986     // Start laying out UP the ownerCt's layout when flagged to do so.
33987     doOwnerCtLayouts: function() {
33988         var owner = this.owner,
33989             ownerCt = owner.ownerCt,
33990             ownerCtComponentLayout, ownerCtContainerLayout,
33991             curSize = this.lastComponentSize,
33992             prevSize = this.previousComponentSize,
33993             widthChange  = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width  !== prevSize.width  : true,
33994             heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
33995
33996         // If size has not changed, do not inform upstream layouts
33997         if (!ownerCt || (!widthChange && !heightChange)) {
33998             return;
33999         }
34000
34001         ownerCtComponentLayout = ownerCt.componentLayout;
34002         ownerCtContainerLayout = ownerCt.layout;
34003
34004         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
34005             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
34006
34007                 // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
34008                 if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
34009                     // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
34010                     this.isSizing = true;
34011                     ownerCt.doComponentLayout();
34012                     this.isSizing = false;
34013                 }
34014                 // Execute upstream Container layout
34015                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
34016                     ownerCtContainerLayout.layout();
34017                 }
34018             }
34019         }
34020     },
34021
34022     doContainerLayout: function() {
34023         var me = this,
34024             owner = me.owner,
34025             ownerCt = owner.ownerCt,
34026             layout = owner.layout,
34027             ownerCtComponentLayout;
34028
34029         // Run the container layout if it exists (layout for child items)
34030         // **Unless automatic laying out is suspended, or the layout is currently running**
34031         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
34032             layout.layout();
34033         }
34034
34035         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
34036         if (ownerCt && ownerCt.componentLayout) {
34037             ownerCtComponentLayout = ownerCt.componentLayout;
34038             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
34039                 ownerCtComponentLayout.childrenChanged = true;
34040             }
34041         }
34042     },
34043
34044     afterLayout : function(width, height, isSetSize, layoutOwner) {
34045         this.doContainerLayout();
34046         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
34047     }
34048 });
34049
34050 /**
34051  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
34052  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
34053  * should not contain any HTML, otherwise it may not be measured correctly.
34054  *
34055  * The measurement works by copying the relevant CSS styles that can affect the font related display, 
34056  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
34057  * provide a **fixed width** when doing the measurement.
34058  *
34059  * If multiple measurements are being done on the same element, you create a new instance to initialize 
34060  * to avoid the overhead of copying the styles to the element repeatedly.
34061  */
34062 Ext.define('Ext.util.TextMetrics', {
34063     statics: {
34064         shared: null,
34065         /**
34066          * Measures the size of the specified text
34067          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
34068          * that can affect the size of the rendered text
34069          * @param {String} text The text to measure
34070          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
34071          * in order to accurately measure the text height
34072          * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
34073          */
34074         measure: function(el, text, fixedWidth){
34075             var me = this,
34076                 shared = me.shared;
34077             
34078             if(!shared){
34079                 shared = me.shared = new me(el, fixedWidth);
34080             }
34081             shared.bind(el);
34082             shared.setFixedWidth(fixedWidth || 'auto');
34083             return shared.getSize(text);
34084         },
34085         
34086         /**
34087           * Destroy the TextMetrics instance created by {@link #measure}.
34088           */
34089          destroy: function(){
34090              var me = this;
34091              Ext.destroy(me.shared);
34092              me.shared = null;
34093          }
34094     },
34095     
34096     /**
34097      * Creates new TextMetrics.
34098      * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
34099      * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
34100      */
34101     constructor: function(bindTo, fixedWidth){
34102         var measure = this.measure = Ext.getBody().createChild({
34103             cls: 'x-textmetrics'
34104         });
34105         this.el = Ext.get(bindTo);
34106         
34107         measure.position('absolute');
34108         measure.setLeftTop(-1000, -1000);
34109         measure.hide();
34110
34111         if (fixedWidth) {
34112            measure.setWidth(fixedWidth);
34113         }
34114     },
34115     
34116     /**
34117      * Returns the size of the specified text based on the internal element's style and width properties
34118      * @param {String} text The text to measure
34119      * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
34120      */
34121     getSize: function(text){
34122         var measure = this.measure,
34123             size;
34124         
34125         measure.update(text);
34126         size = measure.getSize();
34127         measure.update('');
34128         return size;
34129     },
34130     
34131     /**
34132      * Binds this TextMetrics instance to a new element
34133      * @param {String/HTMLElement/Ext.Element} el The element or its ID.
34134      */
34135     bind: function(el){
34136         var me = this;
34137         
34138         me.el = Ext.get(el);
34139         me.measure.setStyle(
34140             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
34141         );
34142     },
34143     
34144     /**
34145      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
34146      * to set a fixed width in order to accurately measure the text height.
34147      * @param {Number} width The width to set on the element
34148      */
34149      setFixedWidth : function(width){
34150          this.measure.setWidth(width);
34151      },
34152      
34153      /**
34154       * Returns the measured width of the specified text
34155       * @param {String} text The text to measure
34156       * @return {Number} width The width in pixels
34157       */
34158      getWidth : function(text){
34159          this.measure.dom.style.width = 'auto';
34160          return this.getSize(text).width;
34161      },
34162      
34163      /**
34164       * Returns the measured height of the specified text
34165       * @param {String} text The text to measure
34166       * @return {Number} height The height in pixels
34167       */
34168      getHeight : function(text){
34169          return this.getSize(text).height;
34170      },
34171      
34172      /**
34173       * Destroy this instance
34174       */
34175      destroy: function(){
34176          var me = this;
34177          me.measure.remove();
34178          delete me.el;
34179          delete me.measure;
34180      }
34181 }, function(){
34182     Ext.Element.addMethods({
34183         /**
34184          * Returns the width in pixels of the passed text, or the width of the text in this Element.
34185          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
34186          * @param {Number} min (optional) The minumum value to return.
34187          * @param {Number} max (optional) The maximum value to return.
34188          * @return {Number} The text width in pixels.
34189          * @member Ext.Element
34190          */
34191         getTextWidth : function(text, min, max){
34192             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
34193         }
34194     });
34195 });
34196
34197 /**
34198  * @class Ext.layout.container.boxOverflow.Scroller
34199  * @extends Ext.layout.container.boxOverflow.None
34200  * @private
34201  */
34202 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
34203
34204     /* Begin Definitions */
34205
34206     extend: 'Ext.layout.container.boxOverflow.None',
34207     requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
34208     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
34209     mixins: {
34210         observable: 'Ext.util.Observable'
34211     },
34212     
34213     /* End Definitions */
34214
34215     /**
34216      * @cfg {Boolean} animateScroll
34217      * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
34218      */
34219     animateScroll: false,
34220
34221     /**
34222      * @cfg {Number} scrollIncrement
34223      * The number of pixels to scroll by on scroller click
34224      */
34225     scrollIncrement: 20,
34226
34227     /**
34228      * @cfg {Number} wheelIncrement
34229      * The number of pixels to increment on mouse wheel scrolling.
34230      */
34231     wheelIncrement: 10,
34232
34233     /**
34234      * @cfg {Number} scrollRepeatInterval
34235      * Number of milliseconds between each scroll while a scroller button is held down
34236      */
34237     scrollRepeatInterval: 60,
34238
34239     /**
34240      * @cfg {Number} scrollDuration
34241      * Number of milliseconds that each scroll animation lasts
34242      */
34243     scrollDuration: 400,
34244
34245     /**
34246      * @cfg {String} beforeCtCls
34247      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
34248      * which must always be present at the leftmost edge of the Container
34249      */
34250
34251     /**
34252      * @cfg {String} afterCtCls
34253      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
34254      * which must always be present at the rightmost edge of the Container
34255      */
34256
34257     /**
34258      * @cfg {String} [scrollerCls='x-box-scroller']
34259      * CSS class added to both scroller elements if enableScroll is used
34260      */
34261     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
34262
34263     /**
34264      * @cfg {String} beforeScrollerCls
34265      * CSS class added to the left scroller element if enableScroll is used
34266      */
34267
34268     /**
34269      * @cfg {String} afterScrollerCls
34270      * CSS class added to the right scroller element if enableScroll is used
34271      */
34272     
34273     constructor: function(layout, config) {
34274         this.layout = layout;
34275         Ext.apply(this, config || {});
34276         
34277         this.addEvents(
34278             /**
34279              * @event scroll
34280              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
34281              * @param {Number} newPosition The new position of the scroller
34282              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
34283              */
34284             'scroll'
34285         );
34286     },
34287     
34288     initCSSClasses: function() {
34289         var me = this,
34290         layout = me.layout;
34291
34292         if (!me.CSSinitialized) {
34293             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
34294             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
34295             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
34296             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
34297             me.CSSinitializes = true;
34298         }
34299     },
34300
34301     handleOverflow: function(calculations, targetSize) {
34302         var me = this,
34303             layout = me.layout,
34304             methodName = 'get' + layout.parallelPrefixCap,
34305             newSize = {};
34306
34307         me.initCSSClasses();
34308         me.callParent(arguments);
34309         this.createInnerElements();
34310         this.showScrollers();
34311         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
34312         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
34313         return { targetSize: newSize };
34314     },
34315
34316     /**
34317      * @private
34318      * Creates the beforeCt and afterCt elements if they have not already been created
34319      */
34320     createInnerElements: function() {
34321         var me = this,
34322             target = me.layout.getRenderTarget();
34323
34324         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
34325         //special items such as scrollers or dropdown menu triggers
34326         if (!me.beforeCt) {
34327             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
34328             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
34329             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
34330             me.createWheelListener();
34331         }
34332     },
34333
34334     /**
34335      * @private
34336      * Sets up an listener to scroll on the layout's innerCt mousewheel event
34337      */
34338     createWheelListener: function() {
34339         this.layout.innerCt.on({
34340             scope     : this,
34341             mousewheel: function(e) {
34342                 e.stopEvent();
34343
34344                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
34345             }
34346         });
34347     },
34348
34349     /**
34350      * @private
34351      */
34352     clearOverflow: function() {
34353         this.hideScrollers();
34354     },
34355
34356     /**
34357      * @private
34358      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
34359      * present. 
34360      */
34361     showScrollers: function() {
34362         this.createScrollers();
34363         this.beforeScroller.show();
34364         this.afterScroller.show();
34365         this.updateScrollButtons();
34366         
34367         this.layout.owner.addClsWithUI('scroller');
34368     },
34369
34370     /**
34371      * @private
34372      * Hides the scroller elements in the beforeCt and afterCt
34373      */
34374     hideScrollers: function() {
34375         if (this.beforeScroller != undefined) {
34376             this.beforeScroller.hide();
34377             this.afterScroller.hide();
34378             
34379             this.layout.owner.removeClsWithUI('scroller');
34380         }
34381     },
34382
34383     /**
34384      * @private
34385      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
34386      */
34387     createScrollers: function() {
34388         if (!this.beforeScroller && !this.afterScroller) {
34389             var before = this.beforeCt.createChild({
34390                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
34391             });
34392
34393             var after = this.afterCt.createChild({
34394                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
34395             });
34396
34397             before.addClsOnOver(this.beforeScrollerCls + '-hover');
34398             after.addClsOnOver(this.afterScrollerCls + '-hover');
34399
34400             before.setVisibilityMode(Ext.Element.DISPLAY);
34401             after.setVisibilityMode(Ext.Element.DISPLAY);
34402
34403             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
34404                 interval: this.scrollRepeatInterval,
34405                 handler : this.scrollLeft,
34406                 scope   : this
34407             });
34408
34409             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
34410                 interval: this.scrollRepeatInterval,
34411                 handler : this.scrollRight,
34412                 scope   : this
34413             });
34414
34415             /**
34416              * @property beforeScroller
34417              * @type Ext.Element
34418              * The left scroller element. Only created when needed.
34419              */
34420             this.beforeScroller = before;
34421
34422             /**
34423              * @property afterScroller
34424              * @type Ext.Element
34425              * The left scroller element. Only created when needed.
34426              */
34427             this.afterScroller = after;
34428         }
34429     },
34430
34431     /**
34432      * @private
34433      */
34434     destroy: function() {
34435         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
34436     },
34437
34438     /**
34439      * @private
34440      * Scrolls left or right by the number of pixels specified
34441      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
34442      */
34443     scrollBy: function(delta, animate) {
34444         this.scrollTo(this.getScrollPosition() + delta, animate);
34445     },
34446
34447     /**
34448      * @private
34449      * @return {Object} Object passed to scrollTo when scrolling
34450      */
34451     getScrollAnim: function() {
34452         return {
34453             duration: this.scrollDuration, 
34454             callback: this.updateScrollButtons, 
34455             scope   : this
34456         };
34457     },
34458
34459     /**
34460      * @private
34461      * Enables or disables each scroller button based on the current scroll position
34462      */
34463     updateScrollButtons: function() {
34464         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
34465             return;
34466         }
34467
34468         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
34469             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
34470             beforeCls  = this.beforeScrollerCls + '-disabled',
34471             afterCls   = this.afterScrollerCls  + '-disabled';
34472         
34473         this.beforeScroller[beforeMeth](beforeCls);
34474         this.afterScroller[afterMeth](afterCls);
34475         this.scrolling = false;
34476     },
34477
34478     /**
34479      * @private
34480      * Returns true if the innerCt scroll is already at its left-most point
34481      * @return {Boolean} True if already at furthest left point
34482      */
34483     atExtremeBefore: function() {
34484         return this.getScrollPosition() === 0;
34485     },
34486
34487     /**
34488      * @private
34489      * Scrolls to the left by the configured amount
34490      */
34491     scrollLeft: function() {
34492         this.scrollBy(-this.scrollIncrement, false);
34493     },
34494
34495     /**
34496      * @private
34497      * Scrolls to the right by the configured amount
34498      */
34499     scrollRight: function() {
34500         this.scrollBy(this.scrollIncrement, false);
34501     },
34502
34503     /**
34504      * Returns the current scroll position of the innerCt element
34505      * @return {Number} The current scroll position
34506      */
34507     getScrollPosition: function(){
34508         var layout = this.layout;
34509         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
34510     },
34511
34512     /**
34513      * @private
34514      * Returns the maximum value we can scrollTo
34515      * @return {Number} The max scroll value
34516      */
34517     getMaxScrollPosition: function() {
34518         var layout = this.layout;
34519         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
34520     },
34521
34522     /**
34523      * @private
34524      * Returns true if the innerCt scroll is already at its right-most point
34525      * @return {Boolean} True if already at furthest right point
34526      */
34527     atExtremeAfter: function() {
34528         return this.getScrollPosition() >= this.getMaxScrollPosition();
34529     },
34530
34531     /**
34532      * @private
34533      * Scrolls to the given position. Performs bounds checking.
34534      * @param {Number} position The position to scroll to. This is constrained.
34535      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
34536      */
34537     scrollTo: function(position, animate) {
34538         var me = this,
34539             layout = me.layout,
34540             oldPosition = me.getScrollPosition(),
34541             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
34542
34543         if (newPosition != oldPosition && !me.scrolling) {
34544             if (animate == undefined) {
34545                 animate = me.animateScroll;
34546             }
34547
34548             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
34549             if (animate) {
34550                 me.scrolling = true;
34551             } else {
34552                 me.scrolling = false;
34553                 me.updateScrollButtons();
34554             }
34555             
34556             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
34557         }
34558     },
34559
34560     /**
34561      * Scrolls to the given component.
34562      * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id 
34563      * or a reference to the component itself.
34564      * @param {Boolean} animate True to animate the scrolling
34565      */
34566     scrollToItem: function(item, animate) {
34567         var me = this,
34568             layout = me.layout,
34569             visibility,
34570             box,
34571             newPos;
34572
34573         item = me.getItem(item);
34574         if (item != undefined) {
34575             visibility = this.getItemVisibility(item);
34576             if (!visibility.fullyVisible) {
34577                 box  = item.getBox(true, true);
34578                 newPos = box[layout.parallelPosition];
34579                 if (visibility.hiddenEnd) {
34580                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
34581                 }
34582                 this.scrollTo(newPos, animate);
34583             }
34584         }
34585     },
34586
34587     /**
34588      * @private
34589      * For a given item in the container, return an object with information on whether the item is visible
34590      * with the current innerCt scroll value.
34591      * @param {Ext.Component} item The item
34592      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
34593      */
34594     getItemVisibility: function(item) {
34595         var me          = this,
34596             box         = me.getItem(item).getBox(true, true),
34597             layout      = me.layout,
34598             itemStart   = box[layout.parallelPosition],
34599             itemEnd     = itemStart + box[layout.parallelPrefix],
34600             scrollStart = me.getScrollPosition(),
34601             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
34602
34603         return {
34604             hiddenStart : itemStart < scrollStart,
34605             hiddenEnd   : itemEnd > scrollEnd,
34606             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
34607         };
34608     }
34609 });
34610 /**
34611  * @class Ext.util.Offset
34612  * @ignore
34613  */
34614 Ext.define('Ext.util.Offset', {
34615
34616     /* Begin Definitions */
34617
34618     statics: {
34619         fromObject: function(obj) {
34620             return new this(obj.x, obj.y);
34621         }
34622     },
34623
34624     /* End Definitions */
34625
34626     constructor: function(x, y) {
34627         this.x = (x != null && !isNaN(x)) ? x : 0;
34628         this.y = (y != null && !isNaN(y)) ? y : 0;
34629
34630         return this;
34631     },
34632
34633     copy: function() {
34634         return new Ext.util.Offset(this.x, this.y);
34635     },
34636
34637     copyFrom: function(p) {
34638         this.x = p.x;
34639         this.y = p.y;
34640     },
34641
34642     toString: function() {
34643         return "Offset[" + this.x + "," + this.y + "]";
34644     },
34645
34646     equals: function(offset) {
34647         if(!(offset instanceof this.statics())) {
34648             Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
34649         }
34650
34651         return (this.x == offset.x && this.y == offset.y);
34652     },
34653
34654     round: function(to) {
34655         if (!isNaN(to)) {
34656             var factor = Math.pow(10, to);
34657             this.x = Math.round(this.x * factor) / factor;
34658             this.y = Math.round(this.y * factor) / factor;
34659         } else {
34660             this.x = Math.round(this.x);
34661             this.y = Math.round(this.y);
34662         }
34663     },
34664
34665     isZero: function() {
34666         return this.x == 0 && this.y == 0;
34667     }
34668 });
34669
34670 /**
34671  * @class Ext.util.KeyNav
34672  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
34673  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
34674  * way to implement custom navigation schemes for any UI component.</p>
34675  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
34676  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
34677  <pre><code>
34678 var nav = new Ext.util.KeyNav("my-element", {
34679     "left" : function(e){
34680         this.moveLeft(e.ctrlKey);
34681     },
34682     "right" : function(e){
34683         this.moveRight(e.ctrlKey);
34684     },
34685     "enter" : function(e){
34686         this.save();
34687     },
34688     scope : this
34689 });
34690 </code></pre>
34691  */
34692 Ext.define('Ext.util.KeyNav', {
34693     
34694     alternateClassName: 'Ext.KeyNav',
34695     
34696     requires: ['Ext.util.KeyMap'],
34697     
34698     statics: {
34699         keyOptions: {
34700             left: 37,
34701             right: 39,
34702             up: 38,
34703             down: 40,
34704             space: 32,
34705             pageUp: 33,
34706             pageDown: 34,
34707             del: 46,
34708             backspace: 8,
34709             home: 36,
34710             end: 35,
34711             enter: 13,
34712             esc: 27,
34713             tab: 9
34714         }
34715     },
34716
34717     /**
34718      * Creates new KeyNav.
34719      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34720      * @param {Object} config The config
34721      */
34722     constructor: function(el, config){
34723         this.setConfig(el, config || {});
34724     },
34725     
34726     /**
34727      * Sets up a configuration for the KeyNav.
34728      * @private
34729      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34730      * @param {Object} config A configuration object as specified in the constructor.
34731      */
34732     setConfig: function(el, config) {
34733         if (this.map) {
34734             this.map.destroy();
34735         }
34736         
34737         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
34738             keys = Ext.util.KeyNav.keyOptions,
34739             scope = config.scope || this,
34740             key;
34741         
34742         this.map = map;
34743         for (key in keys) {
34744             if (keys.hasOwnProperty(key)) {
34745                 if (config[key]) {
34746                     map.addBinding({
34747                         scope: scope,
34748                         key: keys[key],
34749                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
34750                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
34751                     });
34752                 }
34753             }
34754         }
34755         
34756         map.disable();
34757         if (!config.disabled) {
34758             map.enable();
34759         }
34760     },
34761     
34762     /**
34763      * Method for filtering out the map argument
34764      * @private
34765      * @param {Ext.util.KeyMap} map
34766      * @param {Ext.EventObject} event
34767      * @param {Object} options Contains the handler to call
34768      */
34769     handleEvent: function(map, event, handler){
34770         return handler.call(this, event);
34771     },
34772     
34773     /**
34774      * @cfg {Boolean} disabled
34775      * True to disable this KeyNav instance.
34776      */
34777     disabled: false,
34778     
34779     /**
34780      * @cfg {String} defaultEventAction
34781      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
34782      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
34783      * {@link Ext.EventObject#stopPropagation}.
34784      */
34785     defaultEventAction: "stopEvent",
34786     
34787     /**
34788      * @cfg {Boolean} forceKeyDown
34789      * Handle the keydown event instead of keypress.  KeyNav automatically does this for IE since
34790      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
34791      * handle keydown instead of keypress.
34792      */
34793     forceKeyDown: false,
34794     
34795     /**
34796      * Destroy this KeyNav (this is the same as calling disable).
34797      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
34798      */
34799     destroy: function(removeEl){
34800         this.map.destroy(removeEl);
34801         delete this.map;
34802     },
34803
34804     /**
34805      * Enable this KeyNav
34806      */
34807     enable: function() {
34808         this.map.enable();
34809         this.disabled = false;
34810     },
34811
34812     /**
34813      * Disable this KeyNav
34814      */
34815     disable: function() {
34816         this.map.disable();
34817         this.disabled = true;
34818     },
34819     
34820     /**
34821      * Convenience function for setting disabled/enabled by boolean.
34822      * @param {Boolean} disabled
34823      */
34824     setDisabled : function(disabled){
34825         this.map.setDisabled(disabled);
34826         this.disabled = disabled;
34827     },
34828     
34829     /**
34830      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
34831      * as well as the useKeyDown option on the EventManager.
34832      * @return {String} The type of event to listen for.
34833      */
34834     getKeyEvent: function(forceKeyDown){
34835         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
34836     }
34837 });
34838
34839 /**
34840  * @class Ext.fx.Queue
34841  * Animation Queue mixin to handle chaining and queueing by target.
34842  * @private
34843  */
34844
34845 Ext.define('Ext.fx.Queue', {
34846
34847     requires: ['Ext.util.HashMap'],
34848
34849     constructor: function() {
34850         this.targets = Ext.create('Ext.util.HashMap');
34851         this.fxQueue = {};
34852     },
34853
34854     // @private
34855     getFxDefaults: function(targetId) {
34856         var target = this.targets.get(targetId);
34857         if (target) {
34858             return target.fxDefaults;
34859         }
34860         return {};
34861     },
34862
34863     // @private
34864     setFxDefaults: function(targetId, obj) {
34865         var target = this.targets.get(targetId);
34866         if (target) {
34867             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
34868         }
34869     },
34870
34871     // @private
34872     stopAnimation: function(targetId) {
34873         var me = this,
34874             queue = me.getFxQueue(targetId),
34875             ln = queue.length;
34876         while (ln) {
34877             queue[ln - 1].end();
34878             ln--;
34879         }
34880     },
34881
34882     /**
34883      * @private
34884      * Returns current animation object if the element has any effects actively running or queued, else returns false.
34885      */
34886     getActiveAnimation: function(targetId) {
34887         var queue = this.getFxQueue(targetId);
34888         return (queue && !!queue.length) ? queue[0] : false;
34889     },
34890
34891     // @private
34892     hasFxBlock: function(targetId) {
34893         var queue = this.getFxQueue(targetId);
34894         return queue && queue[0] && queue[0].block;
34895     },
34896
34897     // @private get fx queue for passed target, create if needed.
34898     getFxQueue: function(targetId) {
34899         if (!targetId) {
34900             return false;
34901         }
34902         var me = this,
34903             queue = me.fxQueue[targetId],
34904             target = me.targets.get(targetId);
34905
34906         if (!target) {
34907             return false;
34908         }
34909
34910         if (!queue) {
34911             me.fxQueue[targetId] = [];
34912             // GarbageCollector will need to clean up Elements since they aren't currently observable
34913             if (target.type != 'element') {
34914                 target.target.on('destroy', function() {
34915                     me.fxQueue[targetId] = [];
34916                 });
34917             }
34918         }
34919         return me.fxQueue[targetId];
34920     },
34921
34922     // @private
34923     queueFx: function(anim) {
34924         var me = this,
34925             target = anim.target,
34926             queue, ln;
34927
34928         if (!target) {
34929             return;
34930         }
34931
34932         queue = me.getFxQueue(target.getId());
34933         ln = queue.length;
34934
34935         if (ln) {
34936             if (anim.concurrent) {
34937                 anim.paused = false;
34938             }
34939             else {
34940                 queue[ln - 1].on('afteranimate', function() {
34941                     anim.paused = false;
34942                 });
34943             }
34944         }
34945         else {
34946             anim.paused = false;
34947         }
34948         anim.on('afteranimate', function() {
34949             Ext.Array.remove(queue, anim);
34950             if (anim.remove) {
34951                 if (target.type == 'element') {
34952                     var el = Ext.get(target.id);
34953                     if (el) {
34954                         el.remove();
34955                     }
34956                 }
34957             }
34958         }, this);
34959         queue.push(anim);
34960     }
34961 });
34962 /**
34963  * @class Ext.fx.target.Target
34964
34965 This class specifies a generic target for an animation. It provides a wrapper around a
34966 series of different types of objects to allow for a generic animation API.
34967 A target can be a single object or a Composite object containing other objects that are 
34968 to be animated. This class and it's subclasses are generally not created directly, the 
34969 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
34970 the instance to be animated.
34971
34972 The following types of objects can be animated:
34973
34974 - {@link Ext.fx.target.Component Components}
34975 - {@link Ext.fx.target.Element Elements}
34976 - {@link Ext.fx.target.Sprite Sprites}
34977
34978  * @markdown
34979  * @abstract
34980  */
34981 Ext.define('Ext.fx.target.Target', {
34982
34983     isAnimTarget: true,
34984
34985     /**
34986      * Creates new Target.
34987      * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
34988      */
34989     constructor: function(target) {
34990         this.target = target;
34991         this.id = this.getId();
34992     },
34993     
34994     getId: function() {
34995         return this.target.id;
34996     }
34997 });
34998
34999 /**
35000  * @class Ext.fx.target.Sprite
35001  * @extends Ext.fx.target.Target
35002
35003 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
35004 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
35005 and the appropriate target will be created.
35006
35007  * @markdown
35008  */
35009
35010 Ext.define('Ext.fx.target.Sprite', {
35011
35012     /* Begin Definitions */
35013
35014     extend: 'Ext.fx.target.Target',
35015
35016     /* End Definitions */
35017
35018     type: 'draw',
35019
35020     getFromPrim: function(sprite, attr) {
35021         var o;
35022         if (attr == 'translate') {
35023             o = {
35024                 x: sprite.attr.translation.x || 0,
35025                 y: sprite.attr.translation.y || 0
35026             };
35027         }
35028         else if (attr == 'rotate') {
35029             o = {
35030                 degrees: sprite.attr.rotation.degrees || 0,
35031                 x: sprite.attr.rotation.x,
35032                 y: sprite.attr.rotation.y
35033             };
35034         }
35035         else {
35036             o = sprite.attr[attr];
35037         }
35038         return o;
35039     },
35040
35041     getAttr: function(attr, val) {
35042         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
35043     },
35044
35045     setAttr: function(targetData) {
35046         var ln = targetData.length,
35047             spriteArr = [],
35048             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
35049         for (i = 0; i < ln; i++) {
35050             attrs = targetData[i].attrs;
35051             for (attr in attrs) {
35052                 attrArr = attrs[attr];
35053                 ln2 = attrArr.length;
35054                 for (j = 0; j < ln2; j++) {
35055                     spritePtr = attrArr[j][0];
35056                     attPtr = attrArr[j][1];
35057                     if (attr === 'translate') {
35058                         value = {
35059                             x: attPtr.x,
35060                             y: attPtr.y
35061                         };
35062                     }
35063                     else if (attr === 'rotate') {
35064                         x = attPtr.x;
35065                         if (isNaN(x)) {
35066                             x = null;
35067                         }
35068                         y = attPtr.y;
35069                         if (isNaN(y)) {
35070                             y = null;
35071                         }
35072                         value = {
35073                             degrees: attPtr.degrees,
35074                             x: x,
35075                             y: y
35076                         };
35077                     }
35078                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
35079                         value = parseFloat(attPtr);
35080                     }
35081                     else {
35082                         value = attPtr;
35083                     }
35084                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
35085                     if (idx == -1) {
35086                         spriteArr.push([spritePtr, {}]);
35087                         idx = spriteArr.length - 1;
35088                     }
35089                     spriteArr[idx][1][attr] = value;
35090                 }
35091             }
35092         }
35093         ln = spriteArr.length;
35094         for (i = 0; i < ln; i++) {
35095             spritePtr = spriteArr[i];
35096             spritePtr[0].setAttributes(spritePtr[1]);
35097         }
35098         this.target.redraw();
35099     }
35100 });
35101
35102 /**
35103  * @class Ext.fx.target.CompositeSprite
35104  * @extends Ext.fx.target.Sprite
35105
35106 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
35107 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
35108 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
35109 and the appropriate target will be created.
35110
35111  * @markdown
35112  */
35113
35114 Ext.define('Ext.fx.target.CompositeSprite', {
35115
35116     /* Begin Definitions */
35117
35118     extend: 'Ext.fx.target.Sprite',
35119
35120     /* End Definitions */
35121
35122     getAttr: function(attr, val) {
35123         var out = [],
35124             target = this.target;
35125         target.each(function(sprite) {
35126             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
35127         }, this);
35128         return out;
35129     }
35130 });
35131
35132 /**
35133  * @class Ext.fx.target.Component
35134  * @extends Ext.fx.target.Target
35135  * 
35136  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
35137  * created directly, the {@link Ext.Component} will be passed to the animation and
35138  * and the appropriate target will be created.
35139  */
35140 Ext.define('Ext.fx.target.Component', {
35141
35142     /* Begin Definitions */
35143    
35144     extend: 'Ext.fx.target.Target',
35145     
35146     /* End Definitions */
35147
35148     type: 'component',
35149
35150     // Methods to call to retrieve unspecified "from" values from a target Component
35151     getPropMethod: {
35152         top: function() {
35153             return this.getPosition(true)[1];
35154         },
35155         left: function() {
35156             return this.getPosition(true)[0];
35157         },
35158         x: function() {
35159             return this.getPosition()[0];
35160         },
35161         y: function() {
35162             return this.getPosition()[1];
35163         },
35164         height: function() {
35165             return this.getHeight();
35166         },
35167         width: function() {
35168             return this.getWidth();
35169         },
35170         opacity: function() {
35171             return this.el.getStyle('opacity');
35172         }
35173     },
35174
35175     compMethod: {
35176         top: 'setPosition',
35177         left: 'setPosition',
35178         x: 'setPagePosition',
35179         y: 'setPagePosition',
35180         height: 'setSize',
35181         width: 'setSize',
35182         opacity: 'setOpacity'
35183     },
35184
35185     // Read the named attribute from the target Component. Use the defined getter for the attribute
35186     getAttr: function(attr, val) {
35187         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
35188     },
35189
35190     setAttr: function(targetData, isFirstFrame, isLastFrame) {
35191         var me = this,
35192             target = me.target,
35193             ln = targetData.length,
35194             attrs, attr, o, i, j, meth, targets, left, top, w, h;
35195         for (i = 0; i < ln; i++) {
35196             attrs = targetData[i].attrs;
35197             for (attr in attrs) {
35198                 targets = attrs[attr].length;
35199                 meth = {
35200                     setPosition: {},
35201                     setPagePosition: {},
35202                     setSize: {},
35203                     setOpacity: {}
35204                 };
35205                 for (j = 0; j < targets; j++) {
35206                     o = attrs[attr][j];
35207                     // We REALLY want a single function call, so push these down to merge them: eg
35208                     // meth.setPagePosition.target = <targetComponent>
35209                     // meth.setPagePosition['x'] = 100
35210                     // meth.setPagePosition['y'] = 100
35211                     meth[me.compMethod[attr]].target = o[0];
35212                     meth[me.compMethod[attr]][attr] = o[1];
35213                 }
35214                 if (meth.setPosition.target) {
35215                     o = meth.setPosition;
35216                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
35217                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
35218                     o.target.setPosition(left, top);
35219                 }
35220                 if (meth.setPagePosition.target) {
35221                     o = meth.setPagePosition;
35222                     o.target.setPagePosition(o.x, o.y);
35223                 }
35224                 if (meth.setSize.target && meth.setSize.target.el) {
35225                     o = meth.setSize;
35226                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
35227                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
35228                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
35229
35230                     // Only set the size of the Component on the last frame, or if the animation was
35231                     // configured with dynamic: true.
35232                     // In other cases, we just set the target element size.
35233                     // This will result in either clipping if animating a reduction in size, or the revealing of
35234                     // the inner elements of the Component if animating an increase in size.
35235                     // Component's animate function initially resizes to the larger size before resizing the
35236                     // outer element to clip the contents.
35237                     if (isLastFrame || me.dynamic) {
35238                         o.target.componentLayout.childrenChanged = true;
35239
35240                         // Flag if we are being called by an animating layout: use setCalculatedSize
35241                         if (me.layoutAnimation) {
35242                             o.target.setCalculatedSize(w, h);
35243                         } else {
35244                             o.target.setSize(w, h);
35245                         }
35246                     }
35247                     else {
35248                         o.target.el.setSize(w, h);
35249                     }
35250                 }
35251                 if (meth.setOpacity.target) {
35252                     o = meth.setOpacity;
35253                     o.target.el.setStyle('opacity', o.opacity);
35254                 }
35255             }
35256         }
35257     }
35258 });
35259
35260 /**
35261  * @class Ext.fx.CubicBezier
35262  * @ignore
35263  */
35264 Ext.define('Ext.fx.CubicBezier', {
35265
35266     /* Begin Definitions */
35267
35268     singleton: true,
35269
35270     /* End Definitions */
35271
35272     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
35273         var cx = 3 * p1x,
35274             bx = 3 * (p2x - p1x) - cx,
35275             ax = 1 - cx - bx,
35276             cy = 3 * p1y,
35277             by = 3 * (p2y - p1y) - cy,
35278             ay = 1 - cy - by;
35279         function sampleCurveX(t) {
35280             return ((ax * t + bx) * t + cx) * t;
35281         }
35282         function solve(x, epsilon) {
35283             var t = solveCurveX(x, epsilon);
35284             return ((ay * t + by) * t + cy) * t;
35285         }
35286         function solveCurveX(x, epsilon) {
35287             var t0, t1, t2, x2, d2, i;
35288             for (t2 = x, i = 0; i < 8; i++) {
35289                 x2 = sampleCurveX(t2) - x;
35290                 if (Math.abs(x2) < epsilon) {
35291                     return t2;
35292                 }
35293                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
35294                 if (Math.abs(d2) < 1e-6) {
35295                     break;
35296                 }
35297                 t2 = t2 - x2 / d2;
35298             }
35299             t0 = 0;
35300             t1 = 1;
35301             t2 = x;
35302             if (t2 < t0) {
35303                 return t0;
35304             }
35305             if (t2 > t1) {
35306                 return t1;
35307             }
35308             while (t0 < t1) {
35309                 x2 = sampleCurveX(t2);
35310                 if (Math.abs(x2 - x) < epsilon) {
35311                     return t2;
35312                 }
35313                 if (x > x2) {
35314                     t0 = t2;
35315                 } else {
35316                     t1 = t2;
35317                 }
35318                 t2 = (t1 - t0) / 2 + t0;
35319             }
35320             return t2;
35321         }
35322         return solve(t, 1 / (200 * duration));
35323     },
35324
35325     cubicBezier: function(x1, y1, x2, y2) {
35326         var fn = function(pos) {
35327             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
35328         };
35329         fn.toCSS3 = function() {
35330             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
35331         };
35332         fn.reverse = function() {
35333             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
35334         };
35335         return fn;
35336     }
35337 });
35338 /**
35339  * Represents an RGB color and provides helper functions get
35340  * color components in HSL color space.
35341  */
35342 Ext.define('Ext.draw.Color', {
35343
35344     /* Begin Definitions */
35345
35346     /* End Definitions */
35347
35348     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
35349     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
35350     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*/,
35351
35352     /**
35353      * @cfg {Number} lightnessFactor
35354      *
35355      * The default factor to compute the lighter or darker color. Defaults to 0.2.
35356      */
35357     lightnessFactor: 0.2,
35358
35359     /**
35360      * Creates new Color.
35361      * @param {Number} red Red component (0..255)
35362      * @param {Number} green Green component (0..255)
35363      * @param {Number} blue Blue component (0..255)
35364      */
35365     constructor : function(red, green, blue) {
35366         var me = this,
35367             clamp = Ext.Number.constrain;
35368         me.r = clamp(red, 0, 255);
35369         me.g = clamp(green, 0, 255);
35370         me.b = clamp(blue, 0, 255);
35371     },
35372
35373     /**
35374      * Get the red component of the color, in the range 0..255.
35375      * @return {Number}
35376      */
35377     getRed: function() {
35378         return this.r;
35379     },
35380
35381     /**
35382      * Get the green component of the color, in the range 0..255.
35383      * @return {Number}
35384      */
35385     getGreen: function() {
35386         return this.g;
35387     },
35388
35389     /**
35390      * Get the blue component of the color, in the range 0..255.
35391      * @return {Number}
35392      */
35393     getBlue: function() {
35394         return this.b;
35395     },
35396
35397     /**
35398      * Get the RGB values.
35399      * @return {Number[]}
35400      */
35401     getRGB: function() {
35402         var me = this;
35403         return [me.r, me.g, me.b];
35404     },
35405
35406     /**
35407      * Get the equivalent HSL components of the color.
35408      * @return {Number[]}
35409      */
35410     getHSL: function() {
35411         var me = this,
35412             r = me.r / 255,
35413             g = me.g / 255,
35414             b = me.b / 255,
35415             max = Math.max(r, g, b),
35416             min = Math.min(r, g, b),
35417             delta = max - min,
35418             h,
35419             s = 0,
35420             l = 0.5 * (max + min);
35421
35422         // min==max means achromatic (hue is undefined)
35423         if (min != max) {
35424             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
35425             if (r == max) {
35426                 h = 60 * (g - b) / delta;
35427             } else if (g == max) {
35428                 h = 120 + 60 * (b - r) / delta;
35429             } else {
35430                 h = 240 + 60 * (r - g) / delta;
35431             }
35432             if (h < 0) {
35433                 h += 360;
35434             }
35435             if (h >= 360) {
35436                 h -= 360;
35437             }
35438         }
35439         return [h, s, l];
35440     },
35441
35442     /**
35443      * Return a new color that is lighter than this color.
35444      * @param {Number} factor Lighter factor (0..1), default to 0.2
35445      * @return Ext.draw.Color
35446      */
35447     getLighter: function(factor) {
35448         var hsl = this.getHSL();
35449         factor = factor || this.lightnessFactor;
35450         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
35451         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
35452     },
35453
35454     /**
35455      * Return a new color that is darker than this color.
35456      * @param {Number} factor Darker factor (0..1), default to 0.2
35457      * @return Ext.draw.Color
35458      */
35459     getDarker: function(factor) {
35460         factor = factor || this.lightnessFactor;
35461         return this.getLighter(-factor);
35462     },
35463
35464     /**
35465      * Return the color in the hex format, i.e. '#rrggbb'.
35466      * @return {String}
35467      */
35468     toString: function() {
35469         var me = this,
35470             round = Math.round,
35471             r = round(me.r).toString(16),
35472             g = round(me.g).toString(16),
35473             b = round(me.b).toString(16);
35474         r = (r.length == 1) ? '0' + r : r;
35475         g = (g.length == 1) ? '0' + g : g;
35476         b = (b.length == 1) ? '0' + b : b;
35477         return ['#', r, g, b].join('');
35478     },
35479
35480     /**
35481      * Convert a color to hexadecimal format.
35482      *
35483      * **Note:** This method is both static and instance.
35484      *
35485      * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
35486      * Can also be an Array, in this case the function handles the first member.
35487      * @returns {String} The color in hexadecimal format.
35488      * @static
35489      */
35490     toHex: function(color) {
35491         if (Ext.isArray(color)) {
35492             color = color[0];
35493         }
35494         if (!Ext.isString(color)) {
35495             return '';
35496         }
35497         if (color.substr(0, 1) === '#') {
35498             return color;
35499         }
35500         var digits = this.colorToHexRe.exec(color);
35501
35502         if (Ext.isArray(digits)) {
35503             var red = parseInt(digits[2], 10),
35504                 green = parseInt(digits[3], 10),
35505                 blue = parseInt(digits[4], 10),
35506                 rgb = blue | (green << 8) | (red << 16);
35507             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
35508         }
35509         else {
35510             return '';
35511         }
35512     },
35513
35514     /**
35515      * Parse the string and create a new color.
35516      *
35517      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
35518      *
35519      * If the string is not recognized, an undefined will be returned instead.
35520      *
35521      * **Note:** This method is both static and instance.
35522      *
35523      * @param {String} str Color in string.
35524      * @returns Ext.draw.Color
35525      * @static
35526      */
35527     fromString: function(str) {
35528         var values, r, g, b,
35529             parse = parseInt;
35530
35531         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
35532             values = str.match(this.hexRe);
35533             if (values) {
35534                 r = parse(values[1], 16) >> 0;
35535                 g = parse(values[2], 16) >> 0;
35536                 b = parse(values[3], 16) >> 0;
35537                 if (str.length == 4) {
35538                     r += (r * 16);
35539                     g += (g * 16);
35540                     b += (b * 16);
35541                 }
35542             }
35543         }
35544         else {
35545             values = str.match(this.rgbRe);
35546             if (values) {
35547                 r = values[1];
35548                 g = values[2];
35549                 b = values[3];
35550             }
35551         }
35552
35553         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
35554     },
35555
35556     /**
35557      * Returns the gray value (0 to 255) of the color.
35558      *
35559      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
35560      *
35561      * @returns {Number}
35562      */
35563     getGrayscale: function() {
35564         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
35565         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
35566     },
35567
35568     /**
35569      * Create a new color based on the specified HSL values.
35570      *
35571      * **Note:** This method is both static and instance.
35572      *
35573      * @param {Number} h Hue component (0..359)
35574      * @param {Number} s Saturation component (0..1)
35575      * @param {Number} l Lightness component (0..1)
35576      * @returns Ext.draw.Color
35577      * @static
35578      */
35579     fromHSL: function(h, s, l) {
35580         var C, X, m, i, rgb = [],
35581             abs = Math.abs,
35582             floor = Math.floor;
35583
35584         if (s == 0 || h == null) {
35585             // achromatic
35586             rgb = [l, l, l];
35587         }
35588         else {
35589             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
35590             // C is the chroma
35591             // X is the second largest component
35592             // m is the lightness adjustment
35593             h /= 60;
35594             C = s * (1 - abs(2 * l - 1));
35595             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
35596             m = l - C / 2;
35597             switch (floor(h)) {
35598                 case 0:
35599                     rgb = [C, X, 0];
35600                     break;
35601                 case 1:
35602                     rgb = [X, C, 0];
35603                     break;
35604                 case 2:
35605                     rgb = [0, C, X];
35606                     break;
35607                 case 3:
35608                     rgb = [0, X, C];
35609                     break;
35610                 case 4:
35611                     rgb = [X, 0, C];
35612                     break;
35613                 case 5:
35614                     rgb = [C, 0, X];
35615                     break;
35616             }
35617             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
35618         }
35619         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
35620     }
35621 }, function() {
35622     var prototype = this.prototype;
35623
35624     //These functions are both static and instance. TODO: find a more elegant way of copying them
35625     this.addStatics({
35626         fromHSL: function() {
35627             return prototype.fromHSL.apply(prototype, arguments);
35628         },
35629         fromString: function() {
35630             return prototype.fromString.apply(prototype, arguments);
35631         },
35632         toHex: function() {
35633             return prototype.toHex.apply(prototype, arguments);
35634         }
35635     });
35636 });
35637
35638 /**
35639  * @class Ext.dd.StatusProxy
35640  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
35641  * default drag proxy used by all Ext.dd components.
35642  */
35643 Ext.define('Ext.dd.StatusProxy', {
35644     animRepair: false,
35645
35646     /**
35647      * Creates new StatusProxy.
35648      * @param {Object} config (optional) Config object.
35649      */
35650     constructor: function(config){
35651         Ext.apply(this, config);
35652         this.id = this.id || Ext.id();
35653         this.proxy = Ext.createWidget('component', {
35654             floating: true,
35655             stateful: false,
35656             id: this.id,
35657             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
35658                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
35659             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
35660             shadow: !config || config.shadow !== false,
35661             renderTo: document.body
35662         });
35663
35664         this.el = this.proxy.el;
35665         this.el.show();
35666         this.el.setVisibilityMode(Ext.Element.VISIBILITY);
35667         this.el.hide();
35668
35669         this.ghost = Ext.get(this.el.dom.childNodes[1]);
35670         this.dropStatus = this.dropNotAllowed;
35671     },
35672     /**
35673      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
35674      * The CSS class to apply to the status element when drop is allowed.
35675      */
35676     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
35677     /**
35678      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
35679      * The CSS class to apply to the status element when drop is not allowed.
35680      */
35681     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
35682
35683     /**
35684      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
35685      * over the current target element.
35686      * @param {String} cssClass The css class for the new drop status indicator image
35687      */
35688     setStatus : function(cssClass){
35689         cssClass = cssClass || this.dropNotAllowed;
35690         if(this.dropStatus != cssClass){
35691             this.el.replaceCls(this.dropStatus, cssClass);
35692             this.dropStatus = cssClass;
35693         }
35694     },
35695
35696     /**
35697      * Resets the status indicator to the default dropNotAllowed value
35698      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
35699      */
35700     reset : function(clearGhost){
35701         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
35702         this.dropStatus = this.dropNotAllowed;
35703         if(clearGhost){
35704             this.ghost.update("");
35705         }
35706     },
35707
35708     /**
35709      * Updates the contents of the ghost element
35710      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
35711      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
35712      */
35713     update : function(html){
35714         if(typeof html == "string"){
35715             this.ghost.update(html);
35716         }else{
35717             this.ghost.update("");
35718             html.style.margin = "0";
35719             this.ghost.dom.appendChild(html);
35720         }
35721         var el = this.ghost.dom.firstChild;
35722         if(el){
35723             Ext.fly(el).setStyle('float', 'none');
35724         }
35725     },
35726
35727     /**
35728      * Returns the underlying proxy {@link Ext.Layer}
35729      * @return {Ext.Layer} el
35730     */
35731     getEl : function(){
35732         return this.el;
35733     },
35734
35735     /**
35736      * Returns the ghost element
35737      * @return {Ext.Element} el
35738      */
35739     getGhost : function(){
35740         return this.ghost;
35741     },
35742
35743     /**
35744      * Hides the proxy
35745      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
35746      */
35747     hide : function(clear) {
35748         this.proxy.hide();
35749         if (clear) {
35750             this.reset(true);
35751         }
35752     },
35753
35754     /**
35755      * Stops the repair animation if it's currently running
35756      */
35757     stop : function(){
35758         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
35759             this.anim.stop();
35760         }
35761     },
35762
35763     /**
35764      * Displays this proxy
35765      */
35766     show : function() {
35767         this.proxy.show();
35768         this.proxy.toFront();
35769     },
35770
35771     /**
35772      * Force the Layer to sync its shadow and shim positions to the element
35773      */
35774     sync : function(){
35775         this.proxy.el.sync();
35776     },
35777
35778     /**
35779      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
35780      * invalid drop operation by the item being dragged.
35781      * @param {Number[]} xy The XY position of the element ([x, y])
35782      * @param {Function} callback The function to call after the repair is complete.
35783      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
35784      */
35785     repair : function(xy, callback, scope){
35786         this.callback = callback;
35787         this.scope = scope;
35788         if (xy && this.animRepair !== false) {
35789             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
35790             this.el.hideUnders(true);
35791             this.anim = this.el.animate({
35792                 duration: this.repairDuration || 500,
35793                 easing: 'ease-out',
35794                 to: {
35795                     x: xy[0],
35796                     y: xy[1]
35797                 },
35798                 stopAnimation: true,
35799                 callback: this.afterRepair,
35800                 scope: this
35801             });
35802         } else {
35803             this.afterRepair();
35804         }
35805     },
35806
35807     // private
35808     afterRepair : function(){
35809         this.hide(true);
35810         if(typeof this.callback == "function"){
35811             this.callback.call(this.scope || this);
35812         }
35813         this.callback = null;
35814         this.scope = null;
35815     },
35816
35817     destroy: function(){
35818         Ext.destroy(this.ghost, this.proxy, this.el);
35819     }
35820 });
35821 /**
35822  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
35823  * is primarily used internally for the Panel's drag drop implementation, and
35824  * should never need to be created directly.
35825  * @private
35826  */
35827 Ext.define('Ext.panel.Proxy', {
35828
35829     alternateClassName: 'Ext.dd.PanelProxy',
35830
35831     /**
35832      * Creates new panel proxy.
35833      * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
35834      * @param {Object} [config] Config object
35835      */
35836     constructor: function(panel, config){
35837         /**
35838          * @property panel
35839          * @type Ext.panel.Panel
35840          */
35841         this.panel = panel;
35842         this.id = this.panel.id +'-ddproxy';
35843         Ext.apply(this, config);
35844     },
35845
35846     /**
35847      * @cfg {Boolean} insertProxy
35848      * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
35849      * Most Panels are not absolute positioned and therefore we need to reserve this space.
35850      */
35851     insertProxy: true,
35852
35853     // private overrides
35854     setStatus: Ext.emptyFn,
35855     reset: Ext.emptyFn,
35856     update: Ext.emptyFn,
35857     stop: Ext.emptyFn,
35858     sync: Ext.emptyFn,
35859
35860     /**
35861      * Gets the proxy's element
35862      * @return {Ext.Element} The proxy's element
35863      */
35864     getEl: function(){
35865         return this.ghost.el;
35866     },
35867
35868     /**
35869      * Gets the proxy's ghost Panel
35870      * @return {Ext.panel.Panel} The proxy's ghost Panel
35871      */
35872     getGhost: function(){
35873         return this.ghost;
35874     },
35875
35876     /**
35877      * Gets the proxy element. This is the element that represents where the
35878      * Panel was before we started the drag operation.
35879      * @return {Ext.Element} The proxy's element
35880      */
35881     getProxy: function(){
35882         return this.proxy;
35883     },
35884
35885     /**
35886      * Hides the proxy
35887      */
35888     hide : function(){
35889         if (this.ghost) {
35890             if (this.proxy) {
35891                 this.proxy.remove();
35892                 delete this.proxy;
35893             }
35894
35895             // Unghost the Panel, do not move the Panel to where the ghost was
35896             this.panel.unghost(null, false);
35897             delete this.ghost;
35898         }
35899     },
35900
35901     /**
35902      * Shows the proxy
35903      */
35904     show: function(){
35905         if (!this.ghost) {
35906             var panelSize = this.panel.getSize();
35907             this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
35908             this.ghost = this.panel.ghost();
35909             if (this.insertProxy) {
35910                 // bc Panels aren't absolute positioned we need to take up the space
35911                 // of where the panel previously was
35912                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
35913                 this.proxy.setSize(panelSize);
35914             }
35915         }
35916     },
35917
35918     // private
35919     repair: function(xy, callback, scope) {
35920         this.hide();
35921         if (typeof callback == "function") {
35922             callback.call(scope || this);
35923         }
35924     },
35925
35926     /**
35927      * Moves the proxy to a different position in the DOM.  This is typically
35928      * called while dragging the Panel to keep the proxy sync'd to the Panel's
35929      * location.
35930      * @param {HTMLElement} parentNode The proxy's parent DOM node
35931      * @param {HTMLElement} [before] The sibling node before which the
35932      * proxy should be inserted (defaults to the parent's last child if not
35933      * specified)
35934      */
35935     moveProxy : function(parentNode, before){
35936         if (this.proxy) {
35937             parentNode.insertBefore(this.proxy.dom, before);
35938         }
35939     }
35940 });
35941 /**
35942  * @class Ext.layout.component.AbstractDock
35943  * @extends Ext.layout.component.Component
35944  * @private
35945  * This ComponentLayout handles docking for Panels. It takes care of panels that are
35946  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
35947  * an AutoContainerLayout in which this panel get his height based of the CSS or
35948  * or its content.
35949  */
35950
35951 Ext.define('Ext.layout.component.AbstractDock', {
35952
35953     /* Begin Definitions */
35954
35955     extend: 'Ext.layout.component.Component',
35956
35957     /* End Definitions */
35958
35959     type: 'dock',
35960
35961     /**
35962      * @private
35963      * @property autoSizing
35964      * @type Boolean
35965      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
35966      */
35967     autoSizing: true,
35968
35969     beforeLayout: function() {
35970         var returnValue = this.callParent(arguments);
35971         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
35972             this.handleItemBorders();
35973             this.initializedBorders = true;
35974         }
35975         return returnValue;
35976     },
35977     
35978     handleItemBorders: function() {
35979         var owner = this.owner,
35980             body = owner.body,
35981             docked = this.getLayoutItems(),
35982             borders = {
35983                 top: [],
35984                 right: [],
35985                 bottom: [],
35986                 left: []
35987             },
35988             oldBorders = this.borders,
35989             opposites = {
35990                 top: 'bottom',
35991                 right: 'left',
35992                 bottom: 'top',
35993                 left: 'right'
35994             },
35995             i, ln, item, dock, side;
35996
35997         for (i = 0, ln = docked.length; i < ln; i++) {
35998             item = docked[i];
35999             dock = item.dock;
36000             
36001             if (item.ignoreBorderManagement) {
36002                 continue;
36003             }
36004             
36005             if (!borders[dock].satisfied) {
36006                 borders[dock].push(item);
36007                 borders[dock].satisfied = true;
36008             }
36009             
36010             if (!borders.top.satisfied && opposites[dock] !== 'top') {
36011                 borders.top.push(item);
36012             }
36013             if (!borders.right.satisfied && opposites[dock] !== 'right') {
36014                 borders.right.push(item);
36015             }            
36016             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
36017                 borders.bottom.push(item);
36018             }            
36019             if (!borders.left.satisfied && opposites[dock] !== 'left') {
36020                 borders.left.push(item);
36021             }
36022         }
36023
36024         if (oldBorders) {
36025             for (side in oldBorders) {
36026                 if (oldBorders.hasOwnProperty(side)) {
36027                     ln = oldBorders[side].length;
36028                     if (!owner.manageBodyBorders) {
36029                         for (i = 0; i < ln; i++) {
36030                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
36031                         }
36032                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
36033                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
36034                         }                    
36035                     }
36036                     else if (oldBorders[side].satisfied) {
36037                         body.setStyle('border-' + side + '-width', '');
36038                     }
36039                 }
36040             }
36041         }
36042                 
36043         for (side in borders) {
36044             if (borders.hasOwnProperty(side)) {
36045                 ln = borders[side].length;
36046                 if (!owner.manageBodyBorders) {
36047                     for (i = 0; i < ln; i++) {
36048                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
36049                     }
36050                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
36051                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
36052                     }                    
36053                 }
36054                 else if (borders[side].satisfied) {
36055                     body.setStyle('border-' + side + '-width', '1px');
36056                 }
36057             }
36058         }
36059         
36060         this.borders = borders;
36061     },
36062     
36063     /**
36064      * @protected
36065      * @param {Ext.Component} owner The Panel that owns this DockLayout
36066      * @param {Ext.Element} target The target in which we are going to render the docked items
36067      * @param {Array} args The arguments passed to the ComponentLayout.layout method
36068      */
36069     onLayout: function(width, height) {
36070         if (this.onLayout_running) {
36071             return;
36072         }
36073         this.onLayout_running = true;
36074         var me = this,
36075             owner = me.owner,
36076             body = owner.body,
36077             layout = owner.layout,
36078             target = me.getTarget(),
36079             autoWidth = false,
36080             autoHeight = false,
36081             padding, border, frameSize;
36082
36083         // We start of by resetting all the layouts info
36084         var info = me.info = {
36085             boxes: [],
36086             size: {
36087                 width: width,
36088                 height: height
36089             },
36090             bodyBox: {}
36091         };
36092         // Clear isAutoDock flag
36093         delete layout.isAutoDock;
36094
36095         Ext.applyIf(info, me.getTargetInfo());
36096
36097         // We need to bind to the ownerCt whenever we do not have a user set height or width.
36098         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
36099             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
36100                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
36101             }
36102             else {
36103                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
36104             }
36105         }
36106
36107         // Determine if we have an autoHeight or autoWidth.
36108         if (height == null || width == null) {
36109             padding = info.padding;
36110             border = info.border;
36111             frameSize = me.frameSize;
36112
36113             // Auto-everything, clear out any style height/width and read from css
36114             if ((height == null) && (width == null)) {
36115                 autoHeight = true;
36116                 autoWidth = true;
36117                 me.setTargetSize(null);
36118                 me.setBodyBox({width: null, height: null});
36119             }
36120             // Auto-height
36121             else if (height == null) {
36122                 autoHeight = true;
36123                 // Clear any sizing that we already set in a previous layout
36124                 me.setTargetSize(width);
36125                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
36126             // Auto-width
36127             }
36128             else {
36129                 autoWidth = true;
36130                 // Clear any sizing that we already set in a previous layout
36131                 me.setTargetSize(null, height);
36132                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
36133             }
36134
36135             // Run the container
36136             if (layout && layout.isLayout) {
36137                 // Auto-Sized so have the container layout notify the component layout.
36138                 layout.bindToOwnerCtComponent = true;
36139                 // Set flag so we don't do a redundant container layout
36140                 layout.isAutoDock = layout.autoSize !== true;
36141                 layout.layout();
36142
36143                 // If this is an autosized container layout, then we must compensate for a
36144                 // body that is being autosized.  We do not want to adjust the body's size
36145                 // to accommodate the dock items, but rather we will want to adjust the
36146                 // target's size.
36147                 //
36148                 // This is necessary because, particularly in a Box layout, all child items
36149                 // are set with absolute dimensions that are not flexible to the size of its
36150                 // innerCt/target.  So once they are laid out, they are sized for good. By
36151                 // shrinking the body box to accommodate dock items, we're merely cutting off
36152                 // parts of the body.  Not good.  Instead, the target's size should expand
36153                 // to fit the dock items in.  This is valid because the target container is
36154                 // suppose to be autosized to fit everything accordingly.
36155                 info.autoSizedCtLayout = layout.autoSize === true;
36156                 info.autoHeight = autoHeight;
36157                 info.autoWidth = autoWidth;
36158             }
36159
36160             // The dockItems method will add all the top and bottom docked items height
36161             // to the info.panelSize height. That's why we have to call setSize after
36162             // we dock all the items to actually set the panel's width and height.
36163             // We have to do this because the panel body and docked items will be position
36164             // absolute which doesn't stretch the panel.
36165             me.dockItems();
36166             me.setTargetSize(info.size.width, info.size.height);
36167         }
36168         else {
36169             me.setTargetSize(width, height);
36170             me.dockItems();
36171         }
36172         me.callParent(arguments);
36173         this.onLayout_running = false;
36174     },
36175
36176     /**
36177      * @protected
36178      * This method will first update all the information about the docked items,
36179      * body dimensions and position, the panel's total size. It will then
36180      * set all these values on the docked items and panel body.
36181      * @param {Array} items Array containing all the docked items
36182      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
36183      * AutoContainerLayout
36184      */
36185     dockItems : function() {
36186         this.calculateDockBoxes();
36187
36188         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
36189         // information about the body, panel size, and boxes for docked items
36190         // inside a property called info.
36191         var info = this.info,
36192             autoWidth = info.autoWidth,
36193             autoHeight = info.autoHeight,
36194             boxes = info.boxes,
36195             ln = boxes.length,
36196             dock, i, item;
36197
36198         // We are going to loop over all the boxes that were calculated
36199         // and set the position of each item the box belongs to.
36200         for (i = 0; i < ln; i++) {
36201             dock = boxes[i];
36202             item = dock.item;
36203             item.setPosition(dock.x, dock.y);
36204             if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
36205                 // Auto-Sized so have the container layout notify the component layout.
36206                 item.layout.bindToOwnerCtComponent = true;
36207             }
36208         }
36209
36210         // Don't adjust body width/height if the target is using an auto container layout.
36211         // But, we do want to adjust the body size if the container layout is auto sized.
36212         if (!info.autoSizedCtLayout) {
36213             if (autoWidth) {
36214                 info.bodyBox.width = null;
36215             }
36216             if (autoHeight) {
36217                 info.bodyBox.height = null;
36218             }
36219         }
36220
36221         // If the bodyBox has been adjusted because of the docked items
36222         // we will update the dimensions and position of the panel's body.
36223         this.setBodyBox(info.bodyBox);
36224     },
36225
36226     /**
36227      * @protected
36228      * This method will set up some initial information about the panel size and bodybox
36229      * and then loop over all the items you pass it to take care of stretching, aligning,
36230      * dock position and all calculations involved with adjusting the body box.
36231      * @param {Array} items Array containing all the docked items we have to layout
36232      */
36233     calculateDockBoxes : function() {
36234         if (this.calculateDockBoxes_running) {
36235             // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
36236             return;
36237         }
36238         this.calculateDockBoxes_running = true;
36239         // We want to use the Panel's el width, and the Panel's body height as the initial
36240         // size we are going to use in calculateDockBoxes. We also want to account for
36241         // the border of the panel.
36242         var me = this,
36243             target = me.getTarget(),
36244             items = me.getLayoutItems(),
36245             owner = me.owner,
36246             bodyEl = owner.body,
36247             info = me.info,
36248             autoWidth = info.autoWidth,
36249             autoHeight = info.autoHeight,
36250             size = info.size,
36251             ln = items.length,
36252             padding = info.padding,
36253             border = info.border,
36254             frameSize = me.frameSize,
36255             item, i, box, rect;
36256
36257         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
36258         // around the height of the body and the width of the panel.
36259         if (autoHeight) {
36260             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
36261         }
36262         else {
36263             size.height = target.getHeight();
36264         }
36265         if (autoWidth) {
36266             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
36267         }
36268         else {
36269             size.width = target.getWidth();
36270         }
36271
36272         info.bodyBox = {
36273             x: padding.left + frameSize.left,
36274             y: padding.top + frameSize.top,
36275             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
36276             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
36277         };
36278
36279         // Loop over all the docked items
36280         for (i = 0; i < ln; i++) {
36281             item = items[i];
36282             // The initBox method will take care of stretching and alignment
36283             // In some cases it will also layout the dock items to be able to
36284             // get a width or height measurement
36285             box = me.initBox(item);
36286
36287             if (autoHeight === true) {
36288                 box = me.adjustAutoBox(box, i);
36289             }
36290             else {
36291                 box = me.adjustSizedBox(box, i);
36292             }
36293
36294             // Save our box. This allows us to loop over all docked items and do all
36295             // calculations first. Then in one loop we will actually size and position
36296             // all the docked items that have changed.
36297             info.boxes.push(box);
36298         }
36299         this.calculateDockBoxes_running = false;
36300     },
36301
36302     /**
36303      * @protected
36304      * This method will adjust the position of the docked item and adjust the body box
36305      * accordingly.
36306      * @param {Object} box The box containing information about the width and height
36307      * of this docked item
36308      * @param {Number} index The index position of this docked item
36309      * @return {Object} The adjusted box
36310      */
36311     adjustSizedBox : function(box, index) {
36312         var bodyBox = this.info.bodyBox,
36313             frameSize = this.frameSize,
36314             info = this.info,
36315             padding = info.padding,
36316             pos = box.type,
36317             border = info.border;
36318
36319         switch (pos) {
36320             case 'top':
36321                 box.y = bodyBox.y;
36322                 break;
36323
36324             case 'left':
36325                 box.x = bodyBox.x;
36326                 break;
36327
36328             case 'bottom':
36329                 box.y = (bodyBox.y + bodyBox.height) - box.height;
36330                 break;
36331
36332             case 'right':
36333                 box.x = (bodyBox.x + bodyBox.width) - box.width;
36334                 break;
36335         }
36336
36337         if (box.ignoreFrame) {
36338             if (pos == 'bottom') {
36339                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
36340             }
36341             else {
36342                 box.y -= (frameSize.top + padding.top + border.top);
36343             }
36344             if (pos == 'right') {
36345                 box.x += (frameSize.right + padding.right + border.right);
36346             }
36347             else {
36348                 box.x -= (frameSize.left + padding.left + border.left);
36349             }
36350         }
36351
36352         // If this is not an overlaying docked item, we have to adjust the body box
36353         if (!box.overlay) {
36354             switch (pos) {
36355                 case 'top':
36356                     bodyBox.y += box.height;
36357                     bodyBox.height -= box.height;
36358                     break;
36359
36360                 case 'left':
36361                     bodyBox.x += box.width;
36362                     bodyBox.width -= box.width;
36363                     break;
36364
36365                 case 'bottom':
36366                     bodyBox.height -= box.height;
36367                     break;
36368
36369                 case 'right':
36370                     bodyBox.width -= box.width;
36371                     break;
36372             }
36373         }
36374         return box;
36375     },
36376
36377     /**
36378      * @protected
36379      * This method will adjust the position of the docked item inside an AutoContainerLayout
36380      * and adjust the body box accordingly.
36381      * @param {Object} box The box containing information about the width and height
36382      * of this docked item
36383      * @param {Number} index The index position of this docked item
36384      * @return {Object} The adjusted box
36385      */
36386     adjustAutoBox : function (box, index) {
36387         var info = this.info,
36388             owner = this.owner,
36389             bodyBox = info.bodyBox,
36390             size = info.size,
36391             boxes = info.boxes,
36392             boxesLn = boxes.length,
36393             pos = box.type,
36394             frameSize = this.frameSize,
36395             padding = info.padding,
36396             border = info.border,
36397             autoSizedCtLayout = info.autoSizedCtLayout,
36398             ln = (boxesLn < index) ? boxesLn : index,
36399             i, adjustBox;
36400
36401         if (pos == 'top' || pos == 'bottom') {
36402             // This can affect the previously set left and right and bottom docked items
36403             for (i = 0; i < ln; i++) {
36404                 adjustBox = boxes[i];
36405                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
36406                     adjustBox.height += box.height;
36407                 }
36408                 else if (adjustBox.type == 'bottom') {
36409                     adjustBox.y += box.height;
36410                 }
36411             }
36412         }
36413
36414         switch (pos) {
36415             case 'top':
36416                 box.y = bodyBox.y;
36417                 if (!box.overlay) {
36418                     bodyBox.y += box.height;
36419                     if (info.autoHeight) {
36420                         size.height += box.height;
36421                     } else {
36422                         bodyBox.height -= box.height;
36423                     }
36424                 }
36425                 break;
36426
36427             case 'bottom':
36428                 if (!box.overlay) {
36429                     if (info.autoHeight) {
36430                         size.height += box.height;
36431                     } else {
36432                         bodyBox.height -= box.height;
36433                     }
36434                 }
36435                 box.y = (bodyBox.y + bodyBox.height);
36436                 break;
36437
36438             case 'left':
36439                 box.x = bodyBox.x;
36440                 if (!box.overlay) {
36441                     bodyBox.x += box.width;
36442                     if (info.autoWidth) {
36443                         size.width += box.width;
36444                     } else {
36445                         bodyBox.width -= box.width;
36446                     }
36447                 }
36448                 break;
36449
36450             case 'right':
36451                 if (!box.overlay) {
36452                     if (info.autoWidth) {
36453                         size.width += box.width;
36454                     } else {
36455                         bodyBox.width -= box.width;
36456                     }
36457                 }
36458                 box.x = (bodyBox.x + bodyBox.width);
36459                 break;
36460         }
36461
36462         if (box.ignoreFrame) {
36463             if (pos == 'bottom') {
36464                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
36465             }
36466             else {
36467                 box.y -= (frameSize.top + padding.top + border.top);
36468             }
36469             if (pos == 'right') {
36470                 box.x += (frameSize.right + padding.right + border.right);
36471             }
36472             else {
36473                 box.x -= (frameSize.left + padding.left + border.left);
36474             }
36475         }
36476         return box;
36477     },
36478
36479     /**
36480      * @protected
36481      * This method will create a box object, with a reference to the item, the type of dock
36482      * (top, left, bottom, right). It will also take care of stretching and aligning of the
36483      * docked items.
36484      * @param {Ext.Component} item The docked item we want to initialize the box for
36485      * @return {Object} The initial box containing width and height and other useful information
36486      */
36487     initBox : function(item) {
36488         var me = this,
36489             bodyBox = me.info.bodyBox,
36490             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
36491             owner = me.owner,
36492             frameSize = me.frameSize,
36493             info = me.info,
36494             padding = info.padding,
36495             border = info.border,
36496             box = {
36497                 item: item,
36498                 overlay: item.overlay,
36499                 type: item.dock,
36500                 offsets: Ext.Element.parseBox(item.offsets || {}),
36501                 ignoreFrame: item.ignoreParentFrame
36502             };
36503         // First we are going to take care of stretch and align properties for all four dock scenarios.
36504         if (item.stretch !== false) {
36505             box.stretched = true;
36506             if (horizontal) {
36507                 box.x = bodyBox.x + box.offsets.left;
36508                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
36509                 if (box.ignoreFrame) {
36510                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
36511                 }
36512                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
36513             }
36514             else {
36515                 box.y = bodyBox.y + box.offsets.top;
36516                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
36517                 if (box.ignoreFrame) {
36518                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
36519                 }
36520                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
36521
36522                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
36523                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
36524                 if (!Ext.supports.ComputedStyle) {
36525                     item.el.repaint();
36526                 }
36527             }
36528         }
36529         else {
36530             item.doComponentLayout();
36531             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
36532             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
36533             box.y += box.offsets.top;
36534             if (horizontal) {
36535                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
36536                 box.x += box.offsets.left;
36537             }
36538         }
36539
36540         // If we haven't calculated the width or height of the docked item yet
36541         // do so, since we need this for our upcoming calculations
36542         if (box.width === undefined) {
36543             box.width = item.getWidth() + item.el.getMargin('lr');
36544         }
36545         if (box.height === undefined) {
36546             box.height = item.getHeight() + item.el.getMargin('tb');
36547         }
36548
36549         return box;
36550     },
36551
36552     /**
36553      * @protected
36554      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
36555      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
36556      */
36557     getLayoutItems : function() {
36558         var it = this.owner.getDockedItems(),
36559             ln = it.length,
36560             i = 0,
36561             result = [];
36562         for (; i < ln; i++) {
36563             if (it[i].isVisible(true)) {
36564                 result.push(it[i]);
36565             }
36566         }
36567         return result;
36568     },
36569
36570     /**
36571      * @protected
36572      * Render the top and left docked items before any existing DOM nodes in our render target,
36573      * and then render the right and bottom docked items after. This is important, for such things
36574      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
36575      * Our collection of docked items will already be ordered via Panel.getDockedItems().
36576      */
36577     renderItems: function(items, target) {
36578         var cns = target.dom.childNodes,
36579             cnsLn = cns.length,
36580             ln = items.length,
36581             domLn = 0,
36582             i, j, cn, item;
36583
36584         // Calculate the number of DOM nodes in our target that are not our docked items
36585         for (i = 0; i < cnsLn; i++) {
36586             cn = Ext.get(cns[i]);
36587             for (j = 0; j < ln; j++) {
36588                 item = items[j];
36589                 if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
36590                     break;
36591                 }
36592             }
36593
36594             if (j === ln) {
36595                 domLn++;
36596             }
36597         }
36598
36599         // Now we go through our docked items and render/move them
36600         for (i = 0, j = 0; i < ln; i++, j++) {
36601             item = items[i];
36602
36603             // If we're now at the right/bottom docked item, we jump ahead in our
36604             // DOM position, just past the existing DOM nodes.
36605             //
36606             // TODO: This is affected if users provide custom weight values to their
36607             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
36608             // sort operation here, for now, in the name of performance. getDockedItems()
36609             // needs the sort operation not just for this layout-time rendering, but
36610             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
36611             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
36612                 j += domLn;
36613             }
36614
36615             // Same logic as Layout.renderItems()
36616             if (item && !item.rendered) {
36617                 this.renderItem(item, target, j);
36618             }
36619             else if (!this.isValidParent(item, target, j)) {
36620                 this.moveItem(item, target, j);
36621             }
36622         }
36623     },
36624
36625     /**
36626      * @protected
36627      * This function will be called by the dockItems method. Since the body is positioned absolute,
36628      * we need to give it dimensions and a position so that it is in the middle surrounded by
36629      * docked items
36630      * @param {Object} box An object containing new x, y, width and height values for the
36631      * Panel's body
36632      */
36633     setBodyBox : function(box) {
36634         var me = this,
36635             owner = me.owner,
36636             body = owner.body,
36637             info = me.info,
36638             bodyMargin = info.bodyMargin,
36639             padding = info.padding,
36640             border = info.border,
36641             frameSize = me.frameSize;
36642         
36643         // Panel collapse effectively hides the Panel's body, so this is a no-op.
36644         if (owner.collapsed) {
36645             return;
36646         }
36647         
36648         if (Ext.isNumber(box.width)) {
36649             box.width -= bodyMargin.left + bodyMargin.right;
36650         }
36651         
36652         if (Ext.isNumber(box.height)) {
36653             box.height -= bodyMargin.top + bodyMargin.bottom;
36654         }
36655         
36656         me.setElementSize(body, box.width, box.height);
36657         if (Ext.isNumber(box.x)) {
36658             body.setLeft(box.x - padding.left - frameSize.left);
36659         }
36660         if (Ext.isNumber(box.y)) {
36661             body.setTop(box.y - padding.top - frameSize.top);
36662         }
36663     },
36664
36665     /**
36666      * @protected
36667      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
36668      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
36669      * An example of a class added to a dock: right item is x-docked-right
36670      * @param {Ext.Component} item The item we are configuring
36671      */
36672     configureItem : function(item, pos) {
36673         this.callParent(arguments);
36674         if (item.dock == 'top' || item.dock == 'bottom') {
36675             item.layoutManagedWidth = 1;
36676             item.layoutManagedHeight = 2;
36677         } else {
36678             item.layoutManagedWidth = 2;
36679             item.layoutManagedHeight = 1;
36680         }
36681         
36682         item.addCls(Ext.baseCSSPrefix + 'docked');
36683         item.addClsWithUI('docked-' + item.dock);
36684     },
36685
36686     afterRemove : function(item) {
36687         this.callParent(arguments);
36688         if (this.itemCls) {
36689             item.el.removeCls(this.itemCls + '-' + item.dock);
36690         }
36691         var dom = item.el.dom;
36692
36693         if (!item.destroying && dom) {
36694             dom.parentNode.removeChild(dom);
36695         }
36696         this.childrenChanged = true;
36697     }
36698 });
36699 /**
36700  * @class Ext.util.Memento
36701  * This class manages a set of captured properties from an object. These captured properties
36702  * can later be restored to an object.
36703  */
36704 Ext.define('Ext.util.Memento', function () {
36705
36706     function captureOne (src, target, prop) {
36707         src[prop] = target[prop];
36708     }
36709
36710     function removeOne (src, target, prop) {
36711         delete src[prop];
36712     }
36713
36714     function restoreOne (src, target, prop) {
36715         var value = src[prop];
36716         if (value || src.hasOwnProperty(prop)) {
36717             restoreValue(target, prop, value);
36718         }
36719     }
36720
36721     function restoreValue (target, prop, value) {
36722         if (Ext.isDefined(value)) {
36723             target[prop] = value;
36724         } else {
36725             delete target[prop];
36726         }
36727     }
36728
36729     function doMany (doOne, src, target, props) {
36730         if (src) {
36731             if (Ext.isArray(props)) {
36732                 Ext.each(props, function (prop) {
36733                     doOne(src, target, prop);
36734                 });
36735             } else {
36736                 doOne(src, target, props);
36737             }
36738         }
36739     }
36740
36741     return {
36742         /**
36743          * @property data
36744          * The collection of captured properties.
36745          * @private
36746          */
36747         data: null,
36748
36749         /**
36750          * @property target
36751          * The default target object for capture/restore (passed to the constructor).
36752          */
36753         target: null,
36754
36755         /**
36756          * Creates a new memento and optionally captures properties from the target object.
36757          * @param {Object} target The target from which to capture properties. If specified in the
36758          * constructor, this target becomes the default target for all other operations.
36759          * @param {String/String[]} props The property or array of properties to capture.
36760          */
36761         constructor: function (target, props) {
36762             if (target) {
36763                 this.target = target;
36764                 if (props) {
36765                     this.capture(props);
36766                 }
36767             }
36768         },
36769
36770         /**
36771          * Captures the specified properties from the target object in this memento.
36772          * @param {String/String[]} props The property or array of properties to capture.
36773          * @param {Object} target The object from which to capture properties.
36774          */
36775         capture: function (props, target) {
36776             doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
36777         },
36778
36779         /**
36780          * Removes the specified properties from this memento. These properties will not be
36781          * restored later without re-capturing their values.
36782          * @param {String/String[]} props The property or array of properties to remove.
36783          */
36784         remove: function (props) {
36785             doMany(removeOne, this.data, null, props);
36786         },
36787
36788         /**
36789          * Restores the specified properties from this memento to the target object.
36790          * @param {String/String[]} props The property or array of properties to restore.
36791          * @param {Boolean} clear True to remove the restored properties from this memento or
36792          * false to keep them (default is true).
36793          * @param {Object} target The object to which to restore properties.
36794          */
36795         restore: function (props, clear, target) {
36796             doMany(restoreOne, this.data, target || this.target, props);
36797             if (clear !== false) {
36798                 this.remove(props);
36799             }
36800         },
36801
36802         /**
36803          * Restores all captured properties in this memento to the target object.
36804          * @param {Boolean} clear True to remove the restored properties from this memento or
36805          * false to keep them (default is true).
36806          * @param {Object} target The object to which to restore properties.
36807          */
36808         restoreAll: function (clear, target) {
36809             var me = this,
36810                 t = target || this.target;
36811
36812             Ext.Object.each(me.data, function (prop, value) {
36813                 restoreValue(t, prop, value);
36814             });
36815
36816             if (clear !== false) {
36817                 delete me.data;
36818             }
36819         }
36820     };
36821 }());
36822
36823 /**
36824  * @class Ext.app.EventBus
36825  * @private
36826  */
36827 Ext.define('Ext.app.EventBus', {
36828     requires: [
36829         'Ext.util.Event'
36830     ],
36831     mixins: {
36832         observable: 'Ext.util.Observable'
36833     },
36834
36835     constructor: function() {
36836         this.mixins.observable.constructor.call(this);
36837
36838         this.bus = {};
36839
36840         var me = this;
36841         Ext.override(Ext.Component, {
36842             fireEvent: function(ev) {
36843                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
36844                     return me.dispatch.call(me, ev, this, arguments);
36845                 }
36846                 return false;
36847             }
36848         });
36849     },
36850
36851     dispatch: function(ev, target, args) {
36852         var bus = this.bus,
36853             selectors = bus[ev],
36854             selector, controllers, id, events, event, i, ln;
36855
36856         if (selectors) {
36857             // Loop over all the selectors that are bound to this event
36858             for (selector in selectors) {
36859                 // Check if the target matches the selector
36860                 if (target.is(selector)) {
36861                     // Loop over all the controllers that are bound to this selector
36862                     controllers = selectors[selector];
36863                     for (id in controllers) {
36864                         // Loop over all the events that are bound to this selector on this controller
36865                         events = controllers[id];
36866                         for (i = 0, ln = events.length; i < ln; i++) {
36867                             event = events[i];
36868                             // Fire the event!
36869                             if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
36870                                 return false;
36871                             };
36872                         }
36873                     }
36874                 }
36875             }
36876         }
36877     },
36878
36879     control: function(selectors, listeners, controller) {
36880         var bus = this.bus,
36881             selector, fn;
36882
36883         if (Ext.isString(selectors)) {
36884             selector = selectors;
36885             selectors = {};
36886             selectors[selector] = listeners;
36887             this.control(selectors, null, controller);
36888             return;
36889         }
36890
36891         Ext.Object.each(selectors, function(selector, listeners) {
36892             Ext.Object.each(listeners, function(ev, listener) {
36893                 var options = {},
36894                     scope = controller,
36895                     event = Ext.create('Ext.util.Event', controller, ev);
36896
36897                 // Normalize the listener
36898                 if (Ext.isObject(listener)) {
36899                     options = listener;
36900                     listener = options.fn;
36901                     scope = options.scope || controller;
36902                     delete options.fn;
36903                     delete options.scope;
36904                 }
36905
36906                 event.addListener(listener, scope, options);
36907
36908                 // Create the bus tree if it is not there yet
36909                 bus[ev] = bus[ev] || {};
36910                 bus[ev][selector] = bus[ev][selector] || {};
36911                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
36912
36913                 // Push our listener in our bus
36914                 bus[ev][selector][controller.id].push(event);
36915             });
36916         });
36917     }
36918 });
36919 /**
36920  * @class Ext.data.Types
36921  * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
36922  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
36923  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
36924  * of this class.</p>
36925  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
36926  * each type definition must contain three properties:</p>
36927  * <div class="mdetail-params"><ul>
36928  * <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
36929  * to be stored in the Field. The function is passed the collowing parameters:
36930  * <div class="mdetail-params"><ul>
36931  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
36932  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
36933  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
36934  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
36935  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
36936  * </ul></div></div></li>
36937  * <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>
36938  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
36939  * </ul></div>
36940  * <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
36941  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
36942  *<pre><code>
36943 // Add a new Field data type which stores a VELatLong object in the Record.
36944 Ext.data.Types.VELATLONG = {
36945     convert: function(v, data) {
36946         return new VELatLong(data.lat, data.long);
36947     },
36948     sortType: function(v) {
36949         return v.Latitude;  // When sorting, order by latitude
36950     },
36951     type: 'VELatLong'
36952 };
36953 </code></pre>
36954  * <p>Then, when declaring a Model, use: <pre><code>
36955 var types = Ext.data.Types; // allow shorthand type access
36956 Ext.define('Unit',
36957     extend: 'Ext.data.Model',
36958     fields: [
36959         { name: 'unitName', mapping: 'UnitName' },
36960         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
36961         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
36962         { name: 'longitude', mapping: 'long', type: types.FLOAT },
36963         { name: 'position', type: types.VELATLONG }
36964     ]
36965 });
36966 </code></pre>
36967  * @singleton
36968  */
36969 Ext.define('Ext.data.Types', {
36970     singleton: true,
36971     requires: ['Ext.data.SortTypes']
36972 }, function() {
36973     var st = Ext.data.SortTypes;
36974
36975     Ext.apply(Ext.data.Types, {
36976         /**
36977          * @property {RegExp} stripRe
36978          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
36979          * This should be overridden for localization.
36980          */
36981         stripRe: /[\$,%]/g,
36982
36983         /**
36984          * @property {Object} AUTO
36985          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
36986          */
36987         AUTO: {
36988             convert: function(v) {
36989                 return v;
36990             },
36991             sortType: st.none,
36992             type: 'auto'
36993         },
36994
36995         /**
36996          * @property {Object} STRING
36997          * This data type means that the raw data is converted into a String before it is placed into a Record.
36998          */
36999         STRING: {
37000             convert: function(v) {
37001                 var defaultValue = this.useNull ? null : '';
37002                 return (v === undefined || v === null) ? defaultValue : String(v);
37003             },
37004             sortType: st.asUCString,
37005             type: 'string'
37006         },
37007
37008         /**
37009          * @property {Object} INT
37010          * This data type means that the raw data is converted into an integer before it is placed into a Record.
37011          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
37012          */
37013         INT: {
37014             convert: function(v) {
37015                 return v !== undefined && v !== null && v !== '' ?
37016                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
37017             },
37018             sortType: st.none,
37019             type: 'int'
37020         },
37021
37022         /**
37023          * @property {Object} FLOAT
37024          * This data type means that the raw data is converted into a number before it is placed into a Record.
37025          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
37026          */
37027         FLOAT: {
37028             convert: function(v) {
37029                 return v !== undefined && v !== null && v !== '' ?
37030                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
37031             },
37032             sortType: st.none,
37033             type: 'float'
37034         },
37035
37036         /**
37037          * @property {Object} BOOL
37038          * <p>This data type means that the raw data is converted into a boolean before it is placed into
37039          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
37040          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
37041          */
37042         BOOL: {
37043             convert: function(v) {
37044                 if (this.useNull && (v === undefined || v === null || v === '')) {
37045                     return null;
37046                 }
37047                 return v === true || v === 'true' || v == 1;
37048             },
37049             sortType: st.none,
37050             type: 'bool'
37051         },
37052
37053         /**
37054          * @property {Object} DATE
37055          * This data type means that the raw data is converted into a Date before it is placed into a Record.
37056          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
37057          * being applied.
37058          */
37059         DATE: {
37060             convert: function(v) {
37061                 var df = this.dateFormat,
37062                     parsed;
37063
37064                 if (!v) {
37065                     return null;
37066                 }
37067                 if (Ext.isDate(v)) {
37068                     return v;
37069                 }
37070                 if (df) {
37071                     if (df == 'timestamp') {
37072                         return new Date(v*1000);
37073                     }
37074                     if (df == 'time') {
37075                         return new Date(parseInt(v, 10));
37076                     }
37077                     return Ext.Date.parse(v, df);
37078                 }
37079
37080                 parsed = Date.parse(v);
37081                 return parsed ? new Date(parsed) : null;
37082             },
37083             sortType: st.asDate,
37084             type: 'date'
37085         }
37086     });
37087
37088     Ext.apply(Ext.data.Types, {
37089         /**
37090          * @property {Object} BOOLEAN
37091          * <p>This data type means that the raw data is converted into a boolean before it is placed into
37092          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
37093          * <p>The synonym <code>BOOL</code> is equivalent.</p>
37094          */
37095         BOOLEAN: this.BOOL,
37096
37097         /**
37098          * @property {Object} INTEGER
37099          * This data type means that the raw data is converted into an integer before it is placed into a Record.
37100          * <p>The synonym <code>INT</code> is equivalent.</p>
37101          */
37102         INTEGER: this.INT,
37103
37104         /**
37105          * @property {Object} NUMBER
37106          * This data type means that the raw data is converted into a number before it is placed into a Record.
37107          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
37108          */
37109         NUMBER: this.FLOAT
37110     });
37111 });
37112
37113 /**
37114  * @author Ed Spencer
37115  *
37116  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
37117  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
37118  * Ext.data.Model Model}. For example, we might set up a model like this:
37119  *
37120  *     Ext.define('User', {
37121  *         extend: 'Ext.data.Model',
37122  *         fields: [
37123  *             'name', 'email',
37124  *             {name: 'age', type: 'int'},
37125  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
37126  *         ]
37127  *     });
37128  *
37129  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
37130  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
37131  * up with the 'auto' type. It's as if we'd done this instead:
37132  *
37133  *     Ext.define('User', {
37134  *         extend: 'Ext.data.Model',
37135  *         fields: [
37136  *             {name: 'name', type: 'auto'},
37137  *             {name: 'email', type: 'auto'},
37138  *             {name: 'age', type: 'int'},
37139  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
37140  *         ]
37141  *     });
37142  *
37143  * # Types and conversion
37144  *
37145  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
37146  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
37147  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
37148  *
37149  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
37150  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
37151  *
37152  *     Ext.define('User', {
37153  *         extend: 'Ext.data.Model',
37154  *         fields: [
37155  *             'name', 'email',
37156  *             {name: 'age', type: 'int'},
37157  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
37158  *
37159  *             {
37160  *                 name: 'firstName',
37161  *                 convert: function(value, record) {
37162  *                     var fullName  = record.get('name'),
37163  *                         splits    = fullName.split(" "),
37164  *                         firstName = splits[0];
37165  *
37166  *                     return firstName;
37167  *                 }
37168  *             }
37169  *         ]
37170  *     });
37171  *
37172  * Now when we create a new User, the firstName is populated automatically based on the name:
37173  *
37174  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
37175  *
37176  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
37177  *
37178  * In fact, if we log out all of the data inside ed, we'll see this:
37179  *
37180  *     console.log(ed.data);
37181  *
37182  *     //outputs this:
37183  *     {
37184  *         age: 0,
37185  *         email: "",
37186  *         firstName: "Ed",
37187  *         gender: "Unknown",
37188  *         name: "Ed Spencer"
37189  *     }
37190  *
37191  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
37192  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
37193  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
37194  *
37195  *     ed.set('gender', 'Male');
37196  *     ed.get('gender'); //returns 'Male'
37197  *
37198  *     ed.set('age', 25.4);
37199  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
37200  */
37201 Ext.define('Ext.data.Field', {
37202     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
37203     alias: 'data.field',
37204     
37205     constructor : function(config) {
37206         if (Ext.isString(config)) {
37207             config = {name: config};
37208         }
37209         Ext.apply(this, config);
37210         
37211         var types = Ext.data.Types,
37212             st = this.sortType,
37213             t;
37214
37215         if (this.type) {
37216             if (Ext.isString(this.type)) {
37217                 this.type = types[this.type.toUpperCase()] || types.AUTO;
37218             }
37219         } else {
37220             this.type = types.AUTO;
37221         }
37222
37223         // named sortTypes are supported, here we look them up
37224         if (Ext.isString(st)) {
37225             this.sortType = Ext.data.SortTypes[st];
37226         } else if(Ext.isEmpty(st)) {
37227             this.sortType = this.type.sortType;
37228         }
37229
37230         if (!this.convert) {
37231             this.convert = this.type.convert;
37232         }
37233     },
37234     
37235     /**
37236      * @cfg {String} name
37237      *
37238      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
37239      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
37240      *
37241      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
37242      * just a String for the field name.
37243      */
37244     
37245     /**
37246      * @cfg {String/Object} type
37247      *
37248      * The data type for automatic conversion from received data to the *stored* value if
37249      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
37250      * Possible values are
37251      *
37252      * - auto (Default, implies no conversion)
37253      * - string
37254      * - int
37255      * - float
37256      * - boolean
37257      * - date
37258      *
37259      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
37260      *
37261      * Developers may create their own application-specific data types by defining new members of the {@link
37262      * Ext.data.Types} class.
37263      */
37264     
37265     /**
37266      * @cfg {Function} convert
37267      *
37268      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
37269      * It is passed the following parameters:
37270      *
37271      * - **v** : Mixed
37272      *
37273      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
37274      *   defaultValue}`.
37275      *
37276      * - **rec** : Ext.data.Model
37277      *
37278      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
37279      *   at this point as the fields are read in the order that they are defined in your
37280      *   {@link Ext.data.Model#fields fields} array.
37281      *
37282      * Example of convert functions:
37283      *
37284      *     function fullName(v, record){
37285      *         return record.name.last + ', ' + record.name.first;
37286      *     }
37287      *
37288      *     function location(v, record){
37289      *         return !record.city ? '' : (record.city + ', ' + record.state);
37290      *     }
37291      *
37292      *     Ext.define('Dude', {
37293      *         extend: 'Ext.data.Model',
37294      *         fields: [
37295      *             {name: 'fullname',  convert: fullName},
37296      *             {name: 'firstname', mapping: 'name.first'},
37297      *             {name: 'lastname',  mapping: 'name.last'},
37298      *             {name: 'city', defaultValue: 'homeless'},
37299      *             'state',
37300      *             {name: 'location',  convert: location}
37301      *         ]
37302      *     });
37303      *
37304      *     // create the data store
37305      *     var store = Ext.create('Ext.data.Store', {
37306      *         reader: {
37307      *             type: 'json',
37308      *             model: 'Dude',
37309      *             idProperty: 'key',
37310      *             root: 'daRoot',
37311      *             totalProperty: 'total'
37312      *         }
37313      *     });
37314      *
37315      *     var myData = [
37316      *         { key: 1,
37317      *           name: { first: 'Fat',    last:  'Albert' }
37318      *           // notice no city, state provided in data object
37319      *         },
37320      *         { key: 2,
37321      *           name: { first: 'Barney', last:  'Rubble' },
37322      *           city: 'Bedrock', state: 'Stoneridge'
37323      *         },
37324      *         { key: 3,
37325      *           name: { first: 'Cliff',  last:  'Claven' },
37326      *           city: 'Boston',  state: 'MA'
37327      *         }
37328      *     ];
37329      */
37330
37331     /**
37332      * @cfg {String} dateFormat
37333      *
37334      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
37335      *
37336      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
37337      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
37338      * timestamp. See {@link Ext.Date}.
37339      */
37340     dateFormat: null,
37341     
37342     /**
37343      * @cfg {Boolean} useNull
37344      *
37345      * Use when converting received data into a Number type (either int or float). If the value cannot be
37346      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
37347      */
37348     useNull: false,
37349     
37350     /**
37351      * @cfg {Object} defaultValue
37352      *
37353      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
37354      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
37355      * (i.e. undefined). Defaults to "".
37356      */
37357     defaultValue: "",
37358
37359     /**
37360      * @cfg {String/Number} mapping
37361      *
37362      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
37363      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
37364      * as the field name, the mapping may be omitted.
37365      *
37366      * The form of the mapping expression depends on the Reader being used.
37367      *
37368      * - {@link Ext.data.reader.Json}
37369      *
37370      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
37371      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
37372      *
37373      * - {@link Ext.data.reader.Xml}
37374      *
37375      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
37376      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
37377      *
37378      * - {@link Ext.data.reader.Array}
37379      *
37380      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
37381      *   Array position.
37382      *
37383      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
37384      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
37385      * return the desired data.
37386      */
37387     mapping: null,
37388
37389     /**
37390      * @cfg {Function} sortType
37391      *
37392      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
37393      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
37394      *
37395      *     // current sort     after sort we want
37396      *     // +-+------+          +-+------+
37397      *     // |1|First |          |1|First |
37398      *     // |2|Last  |          |3|Second|
37399      *     // |3|Second|          |2|Last  |
37400      *     // +-+------+          +-+------+
37401      *
37402      *     sortType: function(value) {
37403      *        switch (value.toLowerCase()) // native toLowerCase():
37404      *        {
37405      *           case 'first': return 1;
37406      *           case 'second': return 2;
37407      *           default: return 3;
37408      *        }
37409      *     }
37410      */
37411     sortType : null,
37412
37413     /**
37414      * @cfg {String} sortDir
37415      *
37416      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
37417      */
37418     sortDir : "ASC",
37419
37420     /**
37421      * @cfg {Boolean} allowBlank
37422      * @private
37423      *
37424      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
37425      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
37426      */
37427     allowBlank : true,
37428
37429     /**
37430      * @cfg {Boolean} persist
37431      *
37432      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
37433      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
37434      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
37435      */
37436     persist: true
37437 });
37438
37439 /**
37440  * @class Ext.util.AbstractMixedCollection
37441  * @private
37442  */
37443 Ext.define('Ext.util.AbstractMixedCollection', {
37444     requires: ['Ext.util.Filter'],
37445
37446     mixins: {
37447         observable: 'Ext.util.Observable'
37448     },
37449
37450     constructor: function(allowFunctions, keyFn) {
37451         var me = this;
37452
37453         me.items = [];
37454         me.map = {};
37455         me.keys = [];
37456         me.length = 0;
37457
37458         me.addEvents(
37459             /**
37460              * @event clear
37461              * Fires when the collection is cleared.
37462              */
37463             'clear',
37464
37465             /**
37466              * @event add
37467              * Fires when an item is added to the collection.
37468              * @param {Number} index The index at which the item was added.
37469              * @param {Object} o The item added.
37470              * @param {String} key The key associated with the added item.
37471              */
37472             'add',
37473
37474             /**
37475              * @event replace
37476              * Fires when an item is replaced in the collection.
37477              * @param {String} key he key associated with the new added.
37478              * @param {Object} old The item being replaced.
37479              * @param {Object} new The new item.
37480              */
37481             'replace',
37482
37483             /**
37484              * @event remove
37485              * Fires when an item is removed from the collection.
37486              * @param {Object} o The item being removed.
37487              * @param {String} key (optional) The key associated with the removed item.
37488              */
37489             'remove'
37490         );
37491
37492         me.allowFunctions = allowFunctions === true;
37493
37494         if (keyFn) {
37495             me.getKey = keyFn;
37496         }
37497
37498         me.mixins.observable.constructor.call(me);
37499     },
37500
37501     /**
37502      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
37503      * function should add function references to the collection. Defaults to
37504      * <tt>false</tt>.
37505      */
37506     allowFunctions : false,
37507
37508     /**
37509      * Adds an item to the collection. Fires the {@link #add} event when complete.
37510      * @param {String} key <p>The key to associate with the item, or the new item.</p>
37511      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
37512      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
37513      * the MixedCollection will be able to <i>derive</i> the key for the new item.
37514      * In this case just pass the new item in this parameter.</p>
37515      * @param {Object} o The item to add.
37516      * @return {Object} The item added.
37517      */
37518     add : function(key, obj){
37519         var me = this,
37520             myObj = obj,
37521             myKey = key,
37522             old;
37523
37524         if (arguments.length == 1) {
37525             myObj = myKey;
37526             myKey = me.getKey(myObj);
37527         }
37528         if (typeof myKey != 'undefined' && myKey !== null) {
37529             old = me.map[myKey];
37530             if (typeof old != 'undefined') {
37531                 return me.replace(myKey, myObj);
37532             }
37533             me.map[myKey] = myObj;
37534         }
37535         me.length++;
37536         me.items.push(myObj);
37537         me.keys.push(myKey);
37538         me.fireEvent('add', me.length - 1, myObj, myKey);
37539         return myObj;
37540     },
37541
37542     /**
37543       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
37544       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
37545       * to return a different value as in the following examples:<pre><code>
37546 // normal way
37547 var mc = new Ext.util.MixedCollection();
37548 mc.add(someEl.dom.id, someEl);
37549 mc.add(otherEl.dom.id, otherEl);
37550 //and so on
37551
37552 // using getKey
37553 var mc = new Ext.util.MixedCollection();
37554 mc.getKey = function(el){
37555    return el.dom.id;
37556 };
37557 mc.add(someEl);
37558 mc.add(otherEl);
37559
37560 // or via the constructor
37561 var mc = new Ext.util.MixedCollection(false, function(el){
37562    return el.dom.id;
37563 });
37564 mc.add(someEl);
37565 mc.add(otherEl);
37566      * </code></pre>
37567      * @param {Object} item The item for which to find the key.
37568      * @return {Object} The key for the passed item.
37569      */
37570     getKey : function(o){
37571          return o.id;
37572     },
37573
37574     /**
37575      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
37576      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
37577      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
37578      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
37579      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
37580      * with one having the same key value, then just pass the replacement item in this parameter.</p>
37581      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
37582      * with that key.
37583      * @return {Object}  The new item.
37584      */
37585     replace : function(key, o){
37586         var me = this,
37587             old,
37588             index;
37589
37590         if (arguments.length == 1) {
37591             o = arguments[0];
37592             key = me.getKey(o);
37593         }
37594         old = me.map[key];
37595         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
37596              return me.add(key, o);
37597         }
37598         index = me.indexOfKey(key);
37599         me.items[index] = o;
37600         me.map[key] = o;
37601         me.fireEvent('replace', key, old, o);
37602         return o;
37603     },
37604
37605     /**
37606      * Adds all elements of an Array or an Object to the collection.
37607      * @param {Object/Array} objs An Object containing properties which will be added
37608      * to the collection, or an Array of values, each of which are added to the collection.
37609      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
37610      * has been set to <tt>true</tt>.
37611      */
37612     addAll : function(objs){
37613         var me = this,
37614             i = 0,
37615             args,
37616             len,
37617             key;
37618
37619         if (arguments.length > 1 || Ext.isArray(objs)) {
37620             args = arguments.length > 1 ? arguments : objs;
37621             for (len = args.length; i < len; i++) {
37622                 me.add(args[i]);
37623             }
37624         } else {
37625             for (key in objs) {
37626                 if (objs.hasOwnProperty(key)) {
37627                     if (me.allowFunctions || typeof objs[key] != 'function') {
37628                         me.add(key, objs[key]);
37629                     }
37630                 }
37631             }
37632         }
37633     },
37634
37635     /**
37636      * Executes the specified function once for every item in the collection, passing the following arguments:
37637      * <div class="mdetail-params"><ul>
37638      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
37639      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
37640      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
37641      * </ul></div>
37642      * The function should return a boolean value. Returning false from the function will stop the iteration.
37643      * @param {Function} fn The function to execute for each item.
37644      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
37645      */
37646     each : function(fn, scope){
37647         var items = [].concat(this.items), // each safe for removal
37648             i = 0,
37649             len = items.length,
37650             item;
37651
37652         for (; i < len; i++) {
37653             item = items[i];
37654             if (fn.call(scope || item, item, i, len) === false) {
37655                 break;
37656             }
37657         }
37658     },
37659
37660     /**
37661      * Executes the specified function once for every key in the collection, passing each
37662      * key, and its associated item as the first two parameters.
37663      * @param {Function} fn The function to execute for each item.
37664      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37665      */
37666     eachKey : function(fn, scope){
37667         var keys = this.keys,
37668             items = this.items,
37669             i = 0,
37670             len = keys.length;
37671
37672         for (; i < len; i++) {
37673             fn.call(scope || window, keys[i], items[i], i, len);
37674         }
37675     },
37676
37677     /**
37678      * Returns the first item in the collection which elicits a true return value from the
37679      * passed selection function.
37680      * @param {Function} fn The selection function to execute for each item.
37681      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37682      * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
37683      */
37684     findBy : function(fn, scope) {
37685         var keys = this.keys,
37686             items = this.items,
37687             i = 0,
37688             len = items.length;
37689
37690         for (; i < len; i++) {
37691             if (fn.call(scope || window, items[i], keys[i])) {
37692                 return items[i];
37693             }
37694         }
37695         return null;
37696     },
37697
37698     find : function() {
37699         if (Ext.isDefined(Ext.global.console)) {
37700             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
37701         }
37702         return this.findBy.apply(this, arguments);
37703     },
37704
37705     /**
37706      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
37707      * @param {Number} index The index to insert the item at.
37708      * @param {String} key The key to associate with the new item, or the item itself.
37709      * @param {Object} o (optional) If the second parameter was a key, the new item.
37710      * @return {Object} The item inserted.
37711      */
37712     insert : function(index, key, obj){
37713         var me = this,
37714             myKey = key,
37715             myObj = obj;
37716
37717         if (arguments.length == 2) {
37718             myObj = myKey;
37719             myKey = me.getKey(myObj);
37720         }
37721         if (me.containsKey(myKey)) {
37722             me.suspendEvents();
37723             me.removeAtKey(myKey);
37724             me.resumeEvents();
37725         }
37726         if (index >= me.length) {
37727             return me.add(myKey, myObj);
37728         }
37729         me.length++;
37730         Ext.Array.splice(me.items, index, 0, myObj);
37731         if (typeof myKey != 'undefined' && myKey !== null) {
37732             me.map[myKey] = myObj;
37733         }
37734         Ext.Array.splice(me.keys, index, 0, myKey);
37735         me.fireEvent('add', index, myObj, myKey);
37736         return myObj;
37737     },
37738
37739     /**
37740      * Remove an item from the collection.
37741      * @param {Object} o The item to remove.
37742      * @return {Object} The item removed or false if no item was removed.
37743      */
37744     remove : function(o){
37745         return this.removeAt(this.indexOf(o));
37746     },
37747
37748     /**
37749      * Remove all items in the passed array from the collection.
37750      * @param {Array} items An array of items to be removed.
37751      * @return {Ext.util.MixedCollection} this object
37752      */
37753     removeAll : function(items){
37754         Ext.each(items || [], function(item) {
37755             this.remove(item);
37756         }, this);
37757
37758         return this;
37759     },
37760
37761     /**
37762      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
37763      * @param {Number} index The index within the collection of the item to remove.
37764      * @return {Object} The item removed or false if no item was removed.
37765      */
37766     removeAt : function(index){
37767         var me = this,
37768             o,
37769             key;
37770
37771         if (index < me.length && index >= 0) {
37772             me.length--;
37773             o = me.items[index];
37774             Ext.Array.erase(me.items, index, 1);
37775             key = me.keys[index];
37776             if (typeof key != 'undefined') {
37777                 delete me.map[key];
37778             }
37779             Ext.Array.erase(me.keys, index, 1);
37780             me.fireEvent('remove', o, key);
37781             return o;
37782         }
37783         return false;
37784     },
37785
37786     /**
37787      * Removed an item associated with the passed key fom the collection.
37788      * @param {String} key The key of the item to remove.
37789      * @return {Object} The item removed or false if no item was removed.
37790      */
37791     removeAtKey : function(key){
37792         return this.removeAt(this.indexOfKey(key));
37793     },
37794
37795     /**
37796      * Returns the number of items in the collection.
37797      * @return {Number} the number of items in the collection.
37798      */
37799     getCount : function(){
37800         return this.length;
37801     },
37802
37803     /**
37804      * Returns index within the collection of the passed Object.
37805      * @param {Object} o The item to find the index of.
37806      * @return {Number} index of the item. Returns -1 if not found.
37807      */
37808     indexOf : function(o){
37809         return Ext.Array.indexOf(this.items, o);
37810     },
37811
37812     /**
37813      * Returns index within the collection of the passed key.
37814      * @param {String} key The key to find the index of.
37815      * @return {Number} index of the key.
37816      */
37817     indexOfKey : function(key){
37818         return Ext.Array.indexOf(this.keys, key);
37819     },
37820
37821     /**
37822      * Returns the item associated with the passed key OR index.
37823      * Key has priority over index.  This is the equivalent
37824      * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
37825      * @param {String/Number} key The key or index of the item.
37826      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
37827      * If an item was found, but is a Class, returns <tt>null</tt>.
37828      */
37829     get : function(key) {
37830         var me = this,
37831             mk = me.map[key],
37832             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
37833         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
37834     },
37835
37836     /**
37837      * Returns the item at the specified index.
37838      * @param {Number} index The index of the item.
37839      * @return {Object} The item at the specified index.
37840      */
37841     getAt : function(index) {
37842         return this.items[index];
37843     },
37844
37845     /**
37846      * Returns the item associated with the passed key.
37847      * @param {String/Number} key The key of the item.
37848      * @return {Object} The item associated with the passed key.
37849      */
37850     getByKey : function(key) {
37851         return this.map[key];
37852     },
37853
37854     /**
37855      * Returns true if the collection contains the passed Object as an item.
37856      * @param {Object} o  The Object to look for in the collection.
37857      * @return {Boolean} True if the collection contains the Object as an item.
37858      */
37859     contains : function(o){
37860         return Ext.Array.contains(this.items, o);
37861     },
37862
37863     /**
37864      * Returns true if the collection contains the passed Object as a key.
37865      * @param {String} key The key to look for in the collection.
37866      * @return {Boolean} True if the collection contains the Object as a key.
37867      */
37868     containsKey : function(key){
37869         return typeof this.map[key] != 'undefined';
37870     },
37871
37872     /**
37873      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
37874      */
37875     clear : function(){
37876         var me = this;
37877
37878         me.length = 0;
37879         me.items = [];
37880         me.keys = [];
37881         me.map = {};
37882         me.fireEvent('clear');
37883     },
37884
37885     /**
37886      * Returns the first item in the collection.
37887      * @return {Object} the first item in the collection..
37888      */
37889     first : function() {
37890         return this.items[0];
37891     },
37892
37893     /**
37894      * Returns the last item in the collection.
37895      * @return {Object} the last item in the collection..
37896      */
37897     last : function() {
37898         return this.items[this.length - 1];
37899     },
37900
37901     /**
37902      * Collects all of the values of the given property and returns their sum
37903      * @param {String} property The property to sum by
37904      * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
37905      * summing fields in records, where the fields are all stored inside the 'data' object
37906      * @param {Number} [start=0] The record index to start at
37907      * @param {Number} [end=-1] The record index to end at
37908      * @return {Number} The total
37909      */
37910     sum: function(property, root, start, end) {
37911         var values = this.extractValues(property, root),
37912             length = values.length,
37913             sum    = 0,
37914             i;
37915
37916         start = start || 0;
37917         end   = (end || end === 0) ? end : length - 1;
37918
37919         for (i = start; i <= end; i++) {
37920             sum += values[i];
37921         }
37922
37923         return sum;
37924     },
37925
37926     /**
37927      * Collects unique values of a particular property in this MixedCollection
37928      * @param {String} property The property to collect on
37929      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37930      * summing fields in records, where the fields are all stored inside the 'data' object
37931      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
37932      * @return {Array} The unique values
37933      */
37934     collect: function(property, root, allowNull) {
37935         var values = this.extractValues(property, root),
37936             length = values.length,
37937             hits   = {},
37938             unique = [],
37939             value, strValue, i;
37940
37941         for (i = 0; i < length; i++) {
37942             value = values[i];
37943             strValue = String(value);
37944
37945             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
37946                 hits[strValue] = true;
37947                 unique.push(value);
37948             }
37949         }
37950
37951         return unique;
37952     },
37953
37954     /**
37955      * @private
37956      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
37957      * functions like sum and collect.
37958      * @param {String} property The property to extract
37959      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37960      * extracting field data from Model instances, where the fields are stored inside the 'data' object
37961      * @return {Array} The extracted values
37962      */
37963     extractValues: function(property, root) {
37964         var values = this.items;
37965
37966         if (root) {
37967             values = Ext.Array.pluck(values, root);
37968         }
37969
37970         return Ext.Array.pluck(values, property);
37971     },
37972
37973     /**
37974      * Returns a range of items in this collection
37975      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
37976      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
37977      * @return {Array} An array of items
37978      */
37979     getRange : function(start, end){
37980         var me = this,
37981             items = me.items,
37982             range = [],
37983             i;
37984
37985         if (items.length < 1) {
37986             return range;
37987         }
37988
37989         start = start || 0;
37990         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
37991         if (start <= end) {
37992             for (i = start; i <= end; i++) {
37993                 range[range.length] = items[i];
37994             }
37995         } else {
37996             for (i = start; i >= end; i--) {
37997                 range[range.length] = items[i];
37998             }
37999         }
38000         return range;
38001     },
38002
38003     /**
38004      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
38005      * property/value pair with optional parameters for substring matching and case sensitivity. See
38006      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
38007      * MixedCollection can be easily filtered by property like this:</p>
38008 <pre><code>
38009 //create a simple store with a few people defined
38010 var people = new Ext.util.MixedCollection();
38011 people.addAll([
38012     {id: 1, age: 25, name: 'Ed'},
38013     {id: 2, age: 24, name: 'Tommy'},
38014     {id: 3, age: 24, name: 'Arne'},
38015     {id: 4, age: 26, name: 'Aaron'}
38016 ]);
38017
38018 //a new MixedCollection containing only the items where age == 24
38019 var middleAged = people.filter('age', 24);
38020 </code></pre>
38021      *
38022      *
38023      * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
38024      * @param {String/RegExp} value Either string that the property values
38025      * should start with or a RegExp to test against the property
38026      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
38027      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
38028      * @return {Ext.util.MixedCollection} The new filtered collection
38029      */
38030     filter : function(property, value, anyMatch, caseSensitive) {
38031         var filters = [],
38032             filterFn;
38033
38034         //support for the simple case of filtering by property/value
38035         if (Ext.isString(property)) {
38036             filters.push(Ext.create('Ext.util.Filter', {
38037                 property     : property,
38038                 value        : value,
38039                 anyMatch     : anyMatch,
38040                 caseSensitive: caseSensitive
38041             }));
38042         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
38043             filters = filters.concat(property);
38044         }
38045
38046         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
38047         //so here we construct a function that combines these filters by ANDing them together
38048         filterFn = function(record) {
38049             var isMatch = true,
38050                 length = filters.length,
38051                 i;
38052
38053             for (i = 0; i < length; i++) {
38054                 var filter = filters[i],
38055                     fn     = filter.filterFn,
38056                     scope  = filter.scope;
38057
38058                 isMatch = isMatch && fn.call(scope, record);
38059             }
38060
38061             return isMatch;
38062         };
38063
38064         return this.filterBy(filterFn);
38065     },
38066
38067     /**
38068      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
38069      * The passed function will be called with each object in the collection.
38070      * If the function returns true, the value is included otherwise it is filtered.
38071      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
38072      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
38073      * @return {Ext.util.MixedCollection} The new filtered collection
38074      */
38075     filterBy : function(fn, scope) {
38076         var me = this,
38077             newMC  = new this.self(),
38078             keys   = me.keys,
38079             items  = me.items,
38080             length = items.length,
38081             i;
38082
38083         newMC.getKey = me.getKey;
38084
38085         for (i = 0; i < length; i++) {
38086             if (fn.call(scope || me, items[i], keys[i])) {
38087                 newMC.add(keys[i], items[i]);
38088             }
38089         }
38090
38091         return newMC;
38092     },
38093
38094     /**
38095      * Finds the index of the first matching object in this collection by a specific property/value.
38096      * @param {String} property The name of a property on your objects.
38097      * @param {String/RegExp} value A string that the property values
38098      * should start with or a RegExp to test against the property.
38099      * @param {Number} [start=0] The index to start searching at.
38100      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
38101      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
38102      * @return {Number} The matched index or -1
38103      */
38104     findIndex : function(property, value, start, anyMatch, caseSensitive){
38105         if(Ext.isEmpty(value, false)){
38106             return -1;
38107         }
38108         value = this.createValueMatcher(value, anyMatch, caseSensitive);
38109         return this.findIndexBy(function(o){
38110             return o && value.test(o[property]);
38111         }, null, start);
38112     },
38113
38114     /**
38115      * Find the index of the first matching object in this collection by a function.
38116      * If the function returns <i>true</i> it is considered a match.
38117      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
38118      * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
38119      * @param {Number} [start=0] The index to start searching at.
38120      * @return {Number} The matched index or -1
38121      */
38122     findIndexBy : function(fn, scope, start){
38123         var me = this,
38124             keys = me.keys,
38125             items = me.items,
38126             i = start || 0,
38127             len = items.length;
38128
38129         for (; i < len; i++) {
38130             if (fn.call(scope || me, items[i], keys[i])) {
38131                 return i;
38132             }
38133         }
38134         return -1;
38135     },
38136
38137     /**
38138      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
38139      * and by Ext.data.Store#filter
38140      * @private
38141      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
38142      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
38143      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
38144      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
38145      */
38146     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
38147         if (!value.exec) { // not a regex
38148             var er = Ext.String.escapeRegex;
38149             value = String(value);
38150
38151             if (anyMatch === true) {
38152                 value = er(value);
38153             } else {
38154                 value = '^' + er(value);
38155                 if (exactMatch === true) {
38156                     value += '$';
38157                 }
38158             }
38159             value = new RegExp(value, caseSensitive ? '' : 'i');
38160         }
38161         return value;
38162     },
38163
38164     /**
38165      * Creates a shallow copy of this collection
38166      * @return {Ext.util.MixedCollection}
38167      */
38168     clone : function() {
38169         var me = this,
38170             copy = new this.self(),
38171             keys = me.keys,
38172             items = me.items,
38173             i = 0,
38174             len = items.length;
38175
38176         for(; i < len; i++){
38177             copy.add(keys[i], items[i]);
38178         }
38179         copy.getKey = me.getKey;
38180         return copy;
38181     }
38182 });
38183
38184 /**
38185  * @docauthor Tommy Maintz <tommy@sencha.com>
38186  *
38187  * 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}.
38188  *
38189  * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
38190  * is more likely you will want to use one of the component classes that import this mixin, such as
38191  * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
38192  */
38193 Ext.define("Ext.util.Sortable", {
38194     /**
38195      * @property {Boolean} isSortable
38196      * Flag denoting that this object is sortable. Always true.
38197      */
38198     isSortable: true,
38199
38200     /**
38201      * @property {String} defaultSortDirection
38202      * The default sort direction to use if one is not specified.
38203      */
38204     defaultSortDirection: "ASC",
38205
38206     requires: [
38207         'Ext.util.Sorter'
38208     ],
38209
38210     /**
38211      * @property {String} sortRoot
38212      * The property in each item that contains the data to sort.
38213      */
38214
38215     /**
38216      * Performs initialization of this mixin. Component classes using this mixin should call this method during their
38217      * own initialization.
38218      */
38219     initSortable: function() {
38220         var me = this,
38221             sorters = me.sorters;
38222
38223         /**
38224          * @property {Ext.util.MixedCollection} sorters
38225          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
38226          */
38227         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
38228             return item.id || item.property;
38229         });
38230
38231         if (sorters) {
38232             me.sorters.addAll(me.decodeSorters(sorters));
38233         }
38234     },
38235
38236     /**
38237      * Sorts the data in the Store by one or more of its properties. Example usage:
38238      *
38239      *     //sort by a single field
38240      *     myStore.sort('myField', 'DESC');
38241      *
38242      *     //sorting by multiple fields
38243      *     myStore.sort([
38244      *         {
38245      *             property : 'age',
38246      *             direction: 'ASC'
38247      *         },
38248      *         {
38249      *             property : 'name',
38250      *             direction: 'DESC'
38251      *         }
38252      *     ]);
38253      *
38254      * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
38255      * the actual sorting to its internal {@link Ext.util.MixedCollection}.
38256      *
38257      * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
38258      *
38259      *     store.sort('myField');
38260      *     store.sort('myField');
38261      *
38262      * Is equivalent to this code, because Store handles the toggling automatically:
38263      *
38264      *     store.sort('myField', 'ASC');
38265      *     store.sort('myField', 'DESC');
38266      *
38267      * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
38268      * {@link Ext.data.Model Model}, or an array of sorter configurations.
38269      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
38270      * @return {Ext.util.Sorter[]}
38271      */
38272     sort: function(sorters, direction, where, doSort) {
38273         var me = this,
38274             sorter, sorterFn,
38275             newSorters;
38276
38277         if (Ext.isArray(sorters)) {
38278             doSort = where;
38279             where = direction;
38280             newSorters = sorters;
38281         }
38282         else if (Ext.isObject(sorters)) {
38283             doSort = where;
38284             where = direction;
38285             newSorters = [sorters];
38286         }
38287         else if (Ext.isString(sorters)) {
38288             sorter = me.sorters.get(sorters);
38289
38290             if (!sorter) {
38291                 sorter = {
38292                     property : sorters,
38293                     direction: direction
38294                 };
38295                 newSorters = [sorter];
38296             }
38297             else if (direction === undefined) {
38298                 sorter.toggle();
38299             }
38300             else {
38301                 sorter.setDirection(direction);
38302             }
38303         }
38304
38305         if (newSorters && newSorters.length) {
38306             newSorters = me.decodeSorters(newSorters);
38307             if (Ext.isString(where)) {
38308                 if (where === 'prepend') {
38309                     sorters = me.sorters.clone().items;
38310
38311                     me.sorters.clear();
38312                     me.sorters.addAll(newSorters);
38313                     me.sorters.addAll(sorters);
38314                 }
38315                 else {
38316                     me.sorters.addAll(newSorters);
38317                 }
38318             }
38319             else {
38320                 me.sorters.clear();
38321                 me.sorters.addAll(newSorters);
38322             }
38323         }
38324
38325         if (doSort !== false) {
38326             me.onBeforeSort(newSorters);
38327             
38328             sorters = me.sorters.items;
38329             if (sorters.length) {
38330                 //construct an amalgamated sorter function which combines all of the Sorters passed
38331                 sorterFn = function(r1, r2) {
38332                     var result = sorters[0].sort(r1, r2),
38333                         length = sorters.length,
38334                         i;
38335
38336                         //if we have more than one sorter, OR any additional sorter functions together
38337                         for (i = 1; i < length; i++) {
38338                             result = result || sorters[i].sort.call(this, r1, r2);
38339                         }
38340
38341                     return result;
38342                 };
38343
38344                 me.doSort(sorterFn);
38345             }
38346         }
38347
38348         return sorters;
38349     },
38350
38351     onBeforeSort: Ext.emptyFn,
38352
38353     /**
38354      * @private
38355      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
38356      * @param {Object[]} sorters The sorters array
38357      * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
38358      */
38359     decodeSorters: function(sorters) {
38360         if (!Ext.isArray(sorters)) {
38361             if (sorters === undefined) {
38362                 sorters = [];
38363             } else {
38364                 sorters = [sorters];
38365             }
38366         }
38367
38368         var length = sorters.length,
38369             Sorter = Ext.util.Sorter,
38370             fields = this.model ? this.model.prototype.fields : null,
38371             field,
38372             config, i;
38373
38374         for (i = 0; i < length; i++) {
38375             config = sorters[i];
38376
38377             if (!(config instanceof Sorter)) {
38378                 if (Ext.isString(config)) {
38379                     config = {
38380                         property: config
38381                     };
38382                 }
38383
38384                 Ext.applyIf(config, {
38385                     root     : this.sortRoot,
38386                     direction: "ASC"
38387                 });
38388
38389                 //support for 3.x style sorters where a function can be defined as 'fn'
38390                 if (config.fn) {
38391                     config.sorterFn = config.fn;
38392                 }
38393
38394                 //support a function to be passed as a sorter definition
38395                 if (typeof config == 'function') {
38396                     config = {
38397                         sorterFn: config
38398                     };
38399                 }
38400
38401                 // ensure sortType gets pushed on if necessary
38402                 if (fields && !config.transform) {
38403                     field = fields.get(config.property);
38404                     config.transform = field ? field.sortType : undefined;
38405                 }
38406                 sorters[i] = Ext.create('Ext.util.Sorter', config);
38407             }
38408         }
38409
38410         return sorters;
38411     },
38412
38413     getSorters: function() {
38414         return this.sorters.items;
38415     }
38416 });
38417 /**
38418  * @class Ext.util.MixedCollection
38419  * <p>
38420  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
38421  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
38422  * collection can be accessed by index  or via the key. Newly added items are added to
38423  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
38424  * is heavier and provides more functionality. Sample usage:
38425  * <pre><code>
38426 var coll = new Ext.util.MixedCollection();
38427 coll.add('key1', 'val1');
38428 coll.add('key2', 'val2');
38429 coll.add('key3', 'val3');
38430
38431 console.log(coll.get('key1')); // prints 'val1'
38432 console.log(coll.indexOfKey('key3')); // prints 2
38433  * </code></pre>
38434  *
38435  * <p>
38436  * The MixedCollection also has support for sorting and filtering of the values in the collection.
38437  * <pre><code>
38438 var coll = new Ext.util.MixedCollection();
38439 coll.add('key1', 100);
38440 coll.add('key2', -100);
38441 coll.add('key3', 17);
38442 coll.add('key4', 0);
38443 var biggerThanZero = coll.filterBy(function(value){
38444     return value > 0;
38445 });
38446 console.log(biggerThanZero.getCount()); // prints 2
38447  * </code></pre>
38448  * </p>
38449  */
38450 Ext.define('Ext.util.MixedCollection', {
38451     extend: 'Ext.util.AbstractMixedCollection',
38452     mixins: {
38453         sortable: 'Ext.util.Sortable'
38454     },
38455
38456     /**
38457      * Creates new MixedCollection.
38458      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
38459      * function should add function references to the collection. Defaults to
38460      * <tt>false</tt>.
38461      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
38462      * and return the key value for that item.  This is used when available to look up the key on items that
38463      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
38464      * equivalent to providing an implementation for the {@link #getKey} method.
38465      */
38466     constructor: function() {
38467         var me = this;
38468         me.callParent(arguments);
38469         me.addEvents('sort');
38470         me.mixins.sortable.initSortable.call(me);
38471     },
38472
38473     doSort: function(sorterFn) {
38474         this.sortBy(sorterFn);
38475     },
38476
38477     /**
38478      * @private
38479      * Performs the actual sorting based on a direction and a sorting function. Internally,
38480      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
38481      * the sorted array data back into this.items and this.keys
38482      * @param {String} property Property to sort by ('key', 'value', or 'index')
38483      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
38484      * @param {Function} fn (optional) Comparison function that defines the sort order.
38485      * Defaults to sorting by numeric value.
38486      */
38487     _sort : function(property, dir, fn){
38488         var me = this,
38489             i, len,
38490             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
38491
38492             //this is a temporary array used to apply the sorting function
38493             c     = [],
38494             keys  = me.keys,
38495             items = me.items;
38496
38497         //default to a simple sorter function if one is not provided
38498         fn = fn || function(a, b) {
38499             return a - b;
38500         };
38501
38502         //copy all the items into a temporary array, which we will sort
38503         for(i = 0, len = items.length; i < len; i++){
38504             c[c.length] = {
38505                 key  : keys[i],
38506                 value: items[i],
38507                 index: i
38508             };
38509         }
38510
38511         //sort the temporary array
38512         Ext.Array.sort(c, function(a, b){
38513             var v = fn(a[property], b[property]) * dsc;
38514             if(v === 0){
38515                 v = (a.index < b.index ? -1 : 1);
38516             }
38517             return v;
38518         });
38519
38520         //copy the temporary array back into the main this.items and this.keys objects
38521         for(i = 0, len = c.length; i < len; i++){
38522             items[i] = c[i].value;
38523             keys[i]  = c[i].key;
38524         }
38525
38526         me.fireEvent('sort', me);
38527     },
38528
38529     /**
38530      * Sorts the collection by a single sorter function
38531      * @param {Function} sorterFn The function to sort by
38532      */
38533     sortBy: function(sorterFn) {
38534         var me     = this,
38535             items  = me.items,
38536             keys   = me.keys,
38537             length = items.length,
38538             temp   = [],
38539             i;
38540
38541         //first we create a copy of the items array so that we can sort it
38542         for (i = 0; i < length; i++) {
38543             temp[i] = {
38544                 key  : keys[i],
38545                 value: items[i],
38546                 index: i
38547             };
38548         }
38549
38550         Ext.Array.sort(temp, function(a, b) {
38551             var v = sorterFn(a.value, b.value);
38552             if (v === 0) {
38553                 v = (a.index < b.index ? -1 : 1);
38554             }
38555
38556             return v;
38557         });
38558
38559         //copy the temporary array back into the main this.items and this.keys objects
38560         for (i = 0; i < length; i++) {
38561             items[i] = temp[i].value;
38562             keys[i]  = temp[i].key;
38563         }
38564         
38565         me.fireEvent('sort', me, items, keys);
38566     },
38567
38568     /**
38569      * Reorders each of the items based on a mapping from old index to new index. Internally this
38570      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
38571      * @param {Object} mapping Mapping from old item index to new item index
38572      */
38573     reorder: function(mapping) {
38574         var me = this,
38575             items = me.items,
38576             index = 0,
38577             length = items.length,
38578             order = [],
38579             remaining = [],
38580             oldIndex;
38581
38582         me.suspendEvents();
38583
38584         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
38585         for (oldIndex in mapping) {
38586             order[mapping[oldIndex]] = items[oldIndex];
38587         }
38588
38589         for (index = 0; index < length; index++) {
38590             if (mapping[index] == undefined) {
38591                 remaining.push(items[index]);
38592             }
38593         }
38594
38595         for (index = 0; index < length; index++) {
38596             if (order[index] == undefined) {
38597                 order[index] = remaining.shift();
38598             }
38599         }
38600
38601         me.clear();
38602         me.addAll(order);
38603
38604         me.resumeEvents();
38605         me.fireEvent('sort', me);
38606     },
38607
38608     /**
38609      * Sorts this collection by <b>key</b>s.
38610      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
38611      * @param {Function} fn (optional) Comparison function that defines the sort order.
38612      * Defaults to sorting by case insensitive string.
38613      */
38614     sortByKey : function(dir, fn){
38615         this._sort('key', dir, fn || function(a, b){
38616             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
38617             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
38618         });
38619     }
38620 });
38621
38622 /**
38623  * @author Ed Spencer
38624  * @class Ext.data.Errors
38625  * @extends Ext.util.MixedCollection
38626  *
38627  * <p>Wraps a collection of validation error responses and provides convenient functions for
38628  * accessing and errors for specific fields.</p>
38629  *
38630  * <p>Usually this class does not need to be instantiated directly - instances are instead created
38631  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
38632  *
38633 <pre><code>
38634 //validate some existing model instance - in this case it returned 2 failures messages
38635 var errors = myModel.validate();
38636
38637 errors.isValid(); //false
38638
38639 errors.length; //2
38640 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
38641 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
38642 </code></pre>
38643  */
38644 Ext.define('Ext.data.Errors', {
38645     extend: 'Ext.util.MixedCollection',
38646
38647     /**
38648      * Returns true if there are no errors in the collection
38649      * @return {Boolean}
38650      */
38651     isValid: function() {
38652         return this.length === 0;
38653     },
38654
38655     /**
38656      * Returns all of the errors for the given field
38657      * @param {String} fieldName The field to get errors for
38658      * @return {Object[]} All errors for the given field
38659      */
38660     getByField: function(fieldName) {
38661         var errors = [],
38662             error, field, i;
38663
38664         for (i = 0; i < this.length; i++) {
38665             error = this.items[i];
38666
38667             if (error.field == fieldName) {
38668                 errors.push(error);
38669             }
38670         }
38671
38672         return errors;
38673     }
38674 });
38675
38676 /**
38677  * @author Ed Spencer
38678  *
38679  * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
38680  * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
38681  * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
38682  * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
38683  * 
38684  *     Ext.create('Ext.data.Store', {
38685  *         model: 'User',
38686  *         proxy: {
38687  *             type: 'ajax',
38688  *             url : 'users.json',
38689  *             reader: {
38690  *                 type: 'json',
38691  *                 root: 'users'
38692  *             }
38693  *         },
38694  *     });
38695  *     
38696  * The above reader is configured to consume a JSON string that looks something like this:
38697  *  
38698  *     {
38699  *         "success": true,
38700  *         "users": [
38701  *             { "name": "User 1" },
38702  *             { "name": "User 2" }
38703  *         ]
38704  *     }
38705  * 
38706  *
38707  * # Loading Nested Data
38708  *
38709  * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
38710  * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
38711  * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
38712  *
38713  *     Ext.define("User", {
38714  *         extend: 'Ext.data.Model',
38715  *         fields: [
38716  *             'id', 'name'
38717  *         ],
38718  *
38719  *         hasMany: {model: 'Order', name: 'orders'},
38720  *
38721  *         proxy: {
38722  *             type: 'rest',
38723  *             url : 'users.json',
38724  *             reader: {
38725  *                 type: 'json',
38726  *                 root: 'users'
38727  *             }
38728  *         }
38729  *     });
38730  *
38731  *     Ext.define("Order", {
38732  *         extend: 'Ext.data.Model',
38733  *         fields: [
38734  *             'id', 'total'
38735  *         ],
38736  *
38737  *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
38738  *         belongsTo: 'User'
38739  *     });
38740  *
38741  *     Ext.define("OrderItem", {
38742  *         extend: 'Ext.data.Model',
38743  *         fields: [
38744  *             'id', 'price', 'quantity', 'order_id', 'product_id'
38745  *         ],
38746  *
38747  *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
38748  *     });
38749  *
38750  *     Ext.define("Product", {
38751  *         extend: 'Ext.data.Model',
38752  *         fields: [
38753  *             'id', 'name'
38754  *         ],
38755  *
38756  *         hasMany: 'OrderItem'
38757  *     });
38758  *
38759  * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
38760  * Finally, each OrderItem has a single Product. This allows us to consume data like this:
38761  *
38762  *     {
38763  *         "users": [
38764  *             {
38765  *                 "id": 123,
38766  *                 "name": "Ed",
38767  *                 "orders": [
38768  *                     {
38769  *                         "id": 50,
38770  *                         "total": 100,
38771  *                         "order_items": [
38772  *                             {
38773  *                                 "id"      : 20,
38774  *                                 "price"   : 40,
38775  *                                 "quantity": 2,
38776  *                                 "product" : {
38777  *                                     "id": 1000,
38778  *                                     "name": "MacBook Pro"
38779  *                                 }
38780  *                             },
38781  *                             {
38782  *                                 "id"      : 21,
38783  *                                 "price"   : 20,
38784  *                                 "quantity": 3,
38785  *                                 "product" : {
38786  *                                     "id": 1001,
38787  *                                     "name": "iPhone"
38788  *                                 }
38789  *                             }
38790  *                         ]
38791  *                     }
38792  *                 ]
38793  *             }
38794  *         ]
38795  *     }
38796  *
38797  * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
38798  * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
38799  * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
38800  *
38801  *     var store = Ext.create('Ext.data.Store', {
38802  *         model: "User"
38803  *     });
38804  *
38805  *     store.load({
38806  *         callback: function() {
38807  *             //the user that was loaded
38808  *             var user = store.first();
38809  *
38810  *             console.log("Orders for " + user.get('name') + ":")
38811  *
38812  *             //iterate over the Orders for each User
38813  *             user.orders().each(function(order) {
38814  *                 console.log("Order ID: " + order.getId() + ", which contains items:");
38815  *
38816  *                 //iterate over the OrderItems for each Order
38817  *                 order.orderItems().each(function(orderItem) {
38818  *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct
38819  *                     //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
38820  *                     var product = orderItem.getProduct();
38821  *
38822  *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
38823  *                 });
38824  *             });
38825  *         }
38826  *     });
38827  *
38828  * Running the code above results in the following:
38829  *
38830  *     Orders for Ed:
38831  *     Order ID: 50, which contains items:
38832  *     2 orders of MacBook Pro
38833  *     3 orders of iPhone
38834  */
38835 Ext.define('Ext.data.reader.Reader', {
38836     requires: ['Ext.data.ResultSet'],
38837     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
38838     
38839     /**
38840      * @cfg {String} idProperty
38841      * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
38842      * model. If an idProperty is explicitly specified it will override that of the one specified on the model
38843      */
38844
38845     /**
38846      * @cfg {String} totalProperty
38847      * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
38848      * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
38849      */
38850     totalProperty: 'total',
38851
38852     /**
38853      * @cfg {String} successProperty
38854      * Name of the property from which to retrieve the success attribute. Defaults to success. See
38855      * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
38856      */
38857     successProperty: 'success',
38858
38859     /**
38860      * @cfg {String} root
38861      * The name of the property which contains the Array of row objects.  For JSON reader it's dot-separated list
38862      * of property names.  For XML reader it's a CSS selector.  For array reader it's not applicable.
38863      * 
38864      * By default the natural root of the data will be used.  The root Json array, the root XML element, or the array.
38865      *
38866      * The data packet value for this property should be an empty array to clear the data or show no data.
38867      */
38868     root: '',
38869     
38870     /**
38871      * @cfg {String} messageProperty
38872      * The name of the property which contains a response message. This property is optional.
38873      */
38874     
38875     /**
38876      * @cfg {Boolean} implicitIncludes
38877      * True to automatically parse models nested within other models in a response object. See the
38878      * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
38879      */
38880     implicitIncludes: true,
38881     
38882     isReader: true,
38883     
38884     /**
38885      * Creates new Reader.
38886      * @param {Object} config (optional) Config object.
38887      */
38888     constructor: function(config) {
38889         var me = this;
38890         
38891         Ext.apply(me, config || {});
38892         me.fieldCount = 0;
38893         me.model = Ext.ModelManager.getModel(config.model);
38894         if (me.model) {
38895             me.buildExtractors();
38896         }
38897     },
38898
38899     /**
38900      * Sets a new model for the reader.
38901      * @private
38902      * @param {Object} model The model to set.
38903      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
38904      */
38905     setModel: function(model, setOnProxy) {
38906         var me = this;
38907         
38908         me.model = Ext.ModelManager.getModel(model);
38909         me.buildExtractors(true);
38910         
38911         if (setOnProxy && me.proxy) {
38912             me.proxy.setModel(me.model, true);
38913         }
38914     },
38915
38916     /**
38917      * Reads the given response object. This method normalizes the different types of response object that may be passed
38918      * to it, before handing off the reading of records to the {@link #readRecords} function.
38919      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
38920      * @return {Ext.data.ResultSet} The parsed ResultSet object
38921      */
38922     read: function(response) {
38923         var data = response;
38924         
38925         if (response && response.responseText) {
38926             data = this.getResponseData(response);
38927         }
38928         
38929         if (data) {
38930             return this.readRecords(data);
38931         } else {
38932             return this.nullResultSet;
38933         }
38934     },
38935
38936     /**
38937      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
38938      * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
38939      * processing should not be needed.
38940      * @param {Object} data The raw data object
38941      * @return {Ext.data.ResultSet} A ResultSet object
38942      */
38943     readRecords: function(data) {
38944         var me  = this;
38945         
38946         /*
38947          * We check here whether the number of fields has changed since the last read.
38948          * This works around an issue when a Model is used for both a Tree and another
38949          * source, because the tree decorates the model with extra fields and it causes
38950          * issues because the readers aren't notified.
38951          */
38952         if (me.fieldCount !== me.getFields().length) {
38953             me.buildExtractors(true);
38954         }
38955         
38956         /**
38957          * @property {Object} rawData
38958          * The raw data object that was last passed to readRecords. Stored for further processing if needed
38959          */
38960         me.rawData = data;
38961
38962         data = me.getData(data);
38963
38964         // If we pass an array as the data, we dont use getRoot on the data.
38965         // Instead the root equals to the data.
38966         var root    = Ext.isArray(data) ? data : me.getRoot(data),
38967             success = true,
38968             recordCount = 0,
38969             total, value, records, message;
38970             
38971         if (root) {
38972             total = root.length;
38973         }
38974
38975         if (me.totalProperty) {
38976             value = parseInt(me.getTotal(data), 10);
38977             if (!isNaN(value)) {
38978                 total = value;
38979             }
38980         }
38981
38982         if (me.successProperty) {
38983             value = me.getSuccess(data);
38984             if (value === false || value === 'false') {
38985                 success = false;
38986             }
38987         }
38988         
38989         if (me.messageProperty) {
38990             message = me.getMessage(data);
38991         }
38992         
38993         if (root) {
38994             records = me.extractData(root);
38995             recordCount = records.length;
38996         } else {
38997             recordCount = 0;
38998             records = [];
38999         }
39000
39001         return Ext.create('Ext.data.ResultSet', {
39002             total  : total || recordCount,
39003             count  : recordCount,
39004             records: records,
39005             success: success,
39006             message: message
39007         });
39008     },
39009
39010     /**
39011      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
39012      * @param {Object[]/Object} root from server response
39013      * @private
39014      */
39015     extractData : function(root) {
39016         var me = this,
39017             values  = [],
39018             records = [],
39019             Model   = me.model,
39020             i       = 0,
39021             length  = root.length,
39022             idProp  = me.getIdProperty(),
39023             node, id, record;
39024             
39025         if (!root.length && Ext.isObject(root)) {
39026             root = [root];
39027             length = 1;
39028         }
39029
39030         for (; i < length; i++) {
39031             node   = root[i];
39032             values = me.extractValues(node);
39033             id     = me.getId(node);
39034
39035             
39036             record = new Model(values, id, node);
39037             records.push(record);
39038                 
39039             if (me.implicitIncludes) {
39040                 me.readAssociated(record, node);
39041             }
39042         }
39043
39044         return records;
39045     },
39046     
39047     /**
39048      * @private
39049      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
39050      * on the record provided.
39051      * @param {Ext.data.Model} record The record to load associations for
39052      * @param {Object} data The data object
39053      * @return {String} Return value description
39054      */
39055     readAssociated: function(record, data) {
39056         var associations = record.associations.items,
39057             i            = 0,
39058             length       = associations.length,
39059             association, associationData, proxy, reader;
39060         
39061         for (; i < length; i++) {
39062             association     = associations[i];
39063             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
39064             
39065             if (associationData) {
39066                 reader = association.getReader();
39067                 if (!reader) {
39068                     proxy = association.associatedModel.proxy;
39069                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
39070                     if (proxy) {
39071                         reader = proxy.getReader();
39072                     } else {
39073                         reader = new this.constructor({
39074                             model: association.associatedName
39075                         });
39076                     }
39077                 }
39078                 association.read(record, reader, associationData);
39079             }  
39080         }
39081     },
39082     
39083     /**
39084      * @private
39085      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
39086      * record, this should return the relevant part of that data for the given association name. This is only really
39087      * needed to support the XML Reader, which has to do a query to get the associated data object
39088      * @param {Object} data The raw data object
39089      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
39090      * @return {Object} The root
39091      */
39092     getAssociatedDataRoot: function(data, associationName) {
39093         return data[associationName];
39094     },
39095     
39096     getFields: function() {
39097         return this.model.prototype.fields.items;
39098     },
39099
39100     /**
39101      * @private
39102      * Given an object representing a single model instance's data, iterates over the model's fields and
39103      * builds an object with the value for each field.
39104      * @param {Object} data The data object to convert
39105      * @return {Object} Data object suitable for use with a model constructor
39106      */
39107     extractValues: function(data) {
39108         var fields = this.getFields(),
39109             i      = 0,
39110             length = fields.length,
39111             output = {},
39112             field, value;
39113
39114         for (; i < length; i++) {
39115             field = fields[i];
39116             value = this.extractorFunctions[i](data);
39117
39118             output[field.name] = value;
39119         }
39120
39121         return output;
39122     },
39123
39124     /**
39125      * @private
39126      * By default this function just returns what is passed to it. It can be overridden in a subclass
39127      * to return something else. See XmlReader for an example.
39128      * @param {Object} data The data object
39129      * @return {Object} The normalized data object
39130      */
39131     getData: function(data) {
39132         return data;
39133     },
39134
39135     /**
39136      * @private
39137      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
39138      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
39139      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
39140      * @param {Object} data The data object
39141      * @return {Object} The same data object
39142      */
39143     getRoot: function(data) {
39144         return data;
39145     },
39146
39147     /**
39148      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
39149      * implemented by each subclass
39150      * @param {Object} response The responce object
39151      * @return {Object} The useful data from the response
39152      */
39153     getResponseData: function(response) {
39154         Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
39155     },
39156
39157     /**
39158      * @private
39159      * Reconfigures the meta data tied to this Reader
39160      */
39161     onMetaChange : function(meta) {
39162         var fields = meta.fields,
39163             newModel;
39164         
39165         Ext.apply(this, meta);
39166         
39167         if (fields) {
39168             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
39169                 extend: 'Ext.data.Model',
39170                 fields: fields
39171             });
39172             this.setModel(newModel, true);
39173         } else {
39174             this.buildExtractors(true);
39175         }
39176     },
39177     
39178     /**
39179      * Get the idProperty to use for extracting data
39180      * @private
39181      * @return {String} The id property
39182      */
39183     getIdProperty: function(){
39184         var prop = this.idProperty;
39185         if (Ext.isEmpty(prop)) {
39186             prop = this.model.prototype.idProperty;
39187         }
39188         return prop;
39189     },
39190
39191     /**
39192      * @private
39193      * This builds optimized functions for retrieving record data and meta data from an object.
39194      * Subclasses may need to implement their own getRoot function.
39195      * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
39196      */
39197     buildExtractors: function(force) {
39198         var me          = this,
39199             idProp      = me.getIdProperty(),
39200             totalProp   = me.totalProperty,
39201             successProp = me.successProperty,
39202             messageProp = me.messageProperty,
39203             accessor;
39204             
39205         if (force === true) {
39206             delete me.extractorFunctions;
39207         }
39208         
39209         if (me.extractorFunctions) {
39210             return;
39211         }   
39212
39213         //build the extractors for all the meta data
39214         if (totalProp) {
39215             me.getTotal = me.createAccessor(totalProp);
39216         }
39217
39218         if (successProp) {
39219             me.getSuccess = me.createAccessor(successProp);
39220         }
39221
39222         if (messageProp) {
39223             me.getMessage = me.createAccessor(messageProp);
39224         }
39225
39226         if (idProp) {
39227             accessor = me.createAccessor(idProp);
39228
39229             me.getId = function(record) {
39230                 var id = accessor.call(me, record);
39231                 return (id === undefined || id === '') ? null : id;
39232             };
39233         } else {
39234             me.getId = function() {
39235                 return null;
39236             };
39237         }
39238         me.buildFieldExtractors();
39239     },
39240
39241     /**
39242      * @private
39243      */
39244     buildFieldExtractors: function() {
39245         //now build the extractors for all the fields
39246         var me = this,
39247             fields = me.getFields(),
39248             ln = fields.length,
39249             i  = 0,
39250             extractorFunctions = [],
39251             field, map;
39252
39253         for (; i < ln; i++) {
39254             field = fields[i];
39255             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
39256
39257             extractorFunctions.push(me.createAccessor(map));
39258         }
39259         me.fieldCount = ln;
39260
39261         me.extractorFunctions = extractorFunctions;
39262     }
39263 }, function() {
39264     Ext.apply(this, {
39265         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
39266         nullResultSet: Ext.create('Ext.data.ResultSet', {
39267             total  : 0,
39268             count  : 0,
39269             records: [],
39270             success: true
39271         })
39272     });
39273 });
39274 /**
39275  * @author Ed Spencer
39276  * @class Ext.data.reader.Json
39277  * @extends Ext.data.reader.Reader
39278  *
39279  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
39280  * happens as a result of loading a Store - for example we might create something like this:</p>
39281  *
39282 <pre><code>
39283 Ext.define('User', {
39284     extend: 'Ext.data.Model',
39285     fields: ['id', 'name', 'email']
39286 });
39287
39288 var store = Ext.create('Ext.data.Store', {
39289     model: 'User',
39290     proxy: {
39291         type: 'ajax',
39292         url : 'users.json',
39293         reader: {
39294             type: 'json'
39295         }
39296     }
39297 });
39298 </code></pre>
39299  *
39300  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
39301  * not already familiar with them.</p>
39302  *
39303  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
39304  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
39305  * Store, so it is as if we passed this instead:
39306  *
39307 <pre><code>
39308 reader: {
39309     type : 'json',
39310     model: 'User'
39311 }
39312 </code></pre>
39313  *
39314  * <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>
39315  *
39316 <pre><code>
39317 [
39318     {
39319         "id": 1,
39320         "name": "Ed Spencer",
39321         "email": "ed@sencha.com"
39322     },
39323     {
39324         "id": 2,
39325         "name": "Abe Elias",
39326         "email": "abe@sencha.com"
39327     }
39328 ]
39329 </code></pre>
39330  *
39331  * <p><u>Reading other JSON formats</u></p>
39332  *
39333  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
39334  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
39335  * {@link #root} configuration to parse data that comes back like this:</p>
39336  *
39337 <pre><code>
39338 {
39339     "users": [
39340        {
39341            "id": 1,
39342            "name": "Ed Spencer",
39343            "email": "ed@sencha.com"
39344        },
39345        {
39346            "id": 2,
39347            "name": "Abe Elias",
39348            "email": "abe@sencha.com"
39349        }
39350     ]
39351 }
39352 </code></pre>
39353  *
39354  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
39355  *
39356 <pre><code>
39357 reader: {
39358     type: 'json',
39359     root: 'users'
39360 }
39361 </code></pre>
39362  *
39363  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
39364  * around each record inside a nested structure like this:</p>
39365  *
39366 <pre><code>
39367 {
39368     "total": 122,
39369     "offset": 0,
39370     "users": [
39371         {
39372             "id": "ed-spencer-1",
39373             "value": 1,
39374             "user": {
39375                 "id": 1,
39376                 "name": "Ed Spencer",
39377                 "email": "ed@sencha.com"
39378             }
39379         }
39380     ]
39381 }
39382 </code></pre>
39383  *
39384  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
39385  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
39386  * JSON above we need to specify the {@link #record} configuration like this:</p>
39387  *
39388 <pre><code>
39389 reader: {
39390     type  : 'json',
39391     root  : 'users',
39392     record: 'user'
39393 }
39394 </code></pre>
39395  *
39396  * <p><u>Response metadata</u></p>
39397  *
39398  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
39399  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
39400  * like this:</p>
39401  *
39402 <pre><code>
39403 {
39404     "total": 100,
39405     "success": true,
39406     "users": [
39407         {
39408             "id": 1,
39409             "name": "Ed Spencer",
39410             "email": "ed@sencha.com"
39411         }
39412     ]
39413 }
39414 </code></pre>
39415  *
39416  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
39417  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
39418  * options:</p>
39419  *
39420 <pre><code>
39421 reader: {
39422     type : 'json',
39423     root : 'users',
39424     totalProperty  : 'total',
39425     successProperty: 'success'
39426 }
39427 </code></pre>
39428  *
39429  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
39430  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
39431  * returned.</p>
39432  */
39433 Ext.define('Ext.data.reader.Json', {
39434     extend: 'Ext.data.reader.Reader',
39435     alternateClassName: 'Ext.data.JsonReader',
39436     alias : 'reader.json',
39437
39438     root: '',
39439
39440     /**
39441      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
39442      * See the JsonReader intro docs for more details. This is not often needed.
39443      */
39444
39445     /**
39446      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
39447      * reading values. Defalts to <tt>false</tt>.
39448      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
39449      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
39450      * "foo.bar.baz" direct from the root object.
39451      */
39452     useSimpleAccessors: false,
39453
39454     /**
39455      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
39456      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
39457      * @param {Object} data The raw JSON data
39458      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
39459      */
39460     readRecords: function(data) {
39461         //this has to be before the call to super because we use the meta data in the superclass readRecords
39462         if (data.metaData) {
39463             this.onMetaChange(data.metaData);
39464         }
39465
39466         /**
39467          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
39468          * @property {Object} jsonData
39469          */
39470         this.jsonData = data;
39471         return this.callParent([data]);
39472     },
39473
39474     //inherit docs
39475     getResponseData: function(response) {
39476         var data;
39477         try {
39478             data = Ext.decode(response.responseText);
39479         }
39480         catch (ex) {
39481             Ext.Error.raise({
39482                 response: response,
39483                 json: response.responseText,
39484                 parseError: ex,
39485                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
39486             });
39487         }
39488         if (!data) {
39489             Ext.Error.raise('JSON object not found');
39490         }
39491
39492         return data;
39493     },
39494
39495     //inherit docs
39496     buildExtractors : function() {
39497         var me = this;
39498
39499         me.callParent(arguments);
39500
39501         if (me.root) {
39502             me.getRoot = me.createAccessor(me.root);
39503         } else {
39504             me.getRoot = function(root) {
39505                 return root;
39506             };
39507         }
39508     },
39509
39510     /**
39511      * @private
39512      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
39513      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
39514      * @param {Object} root The JSON root node
39515      * @return {Ext.data.Model[]} The records
39516      */
39517     extractData: function(root) {
39518         var recordName = this.record,
39519             data = [],
39520             length, i;
39521
39522         if (recordName) {
39523             length = root.length;
39524             
39525             if (!length && Ext.isObject(root)) {
39526                 length = 1;
39527                 root = [root];
39528             }
39529
39530             for (i = 0; i < length; i++) {
39531                 data[i] = root[i][recordName];
39532             }
39533         } else {
39534             data = root;
39535         }
39536         return this.callParent([data]);
39537     },
39538
39539     /**
39540      * @private
39541      * Returns an accessor function for the given property string. Gives support for properties such as the following:
39542      * 'someProperty'
39543      * 'some.property'
39544      * 'some["property"]'
39545      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
39546      */
39547     createAccessor: function() {
39548         var re = /[\[\.]/;
39549
39550         return function(expr) {
39551             if (Ext.isEmpty(expr)) {
39552                 return Ext.emptyFn;
39553             }
39554             if (Ext.isFunction(expr)) {
39555                 return expr;
39556             }
39557             if (this.useSimpleAccessors !== true) {
39558                 var i = String(expr).search(re);
39559                 if (i >= 0) {
39560                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
39561                 }
39562             }
39563             return function(obj) {
39564                 return obj[expr];
39565             };
39566         };
39567     }()
39568 });
39569 /**
39570  * @class Ext.data.writer.Json
39571  * @extends Ext.data.writer.Writer
39572
39573 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
39574 The {@link #allowSingle} configuration can be set to false to force the records to always be
39575 encoded in an array, even if there is only a single record being sent.
39576
39577  * @markdown
39578  */
39579 Ext.define('Ext.data.writer.Json', {
39580     extend: 'Ext.data.writer.Writer',
39581     alternateClassName: 'Ext.data.JsonWriter',
39582     alias: 'writer.json',
39583     
39584     /**
39585      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
39586      * Example generated request, using root: 'records':
39587 <pre><code>
39588 {'records': [{name: 'my record'}, {name: 'another record'}]}
39589 </code></pre>
39590      */
39591     root: undefined,
39592     
39593     /**
39594      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
39595      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
39596      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
39597      * sent to the server.
39598      */
39599     encode: false,
39600     
39601     /**
39602      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
39603      * one record being sent. When there is more than one record, they will always be encoded into an array.
39604      * Defaults to <tt>true</tt>. Example:
39605      * <pre><code>
39606 // with allowSingle: true
39607 "root": {
39608     "first": "Mark",
39609     "last": "Corrigan"
39610 }
39611
39612 // with allowSingle: false
39613 "root": [{
39614     "first": "Mark",
39615     "last": "Corrigan"
39616 }]
39617      * </code></pre>
39618      */
39619     allowSingle: true,
39620     
39621     //inherit docs
39622     writeRecords: function(request, data) {
39623         var root = this.root;
39624         
39625         if (this.allowSingle && data.length == 1) {
39626             // convert to single object format
39627             data = data[0];
39628         }
39629         
39630         if (this.encode) {
39631             if (root) {
39632                 // sending as a param, need to encode
39633                 request.params[root] = Ext.encode(data);
39634             } else {
39635                 Ext.Error.raise('Must specify a root when using encode');
39636             }
39637         } else {
39638             // send as jsonData
39639             request.jsonData = request.jsonData || {};
39640             if (root) {
39641                 request.jsonData[root] = data;
39642             } else {
39643                 request.jsonData = data;
39644             }
39645         }
39646         return request;
39647     }
39648 });
39649
39650 /**
39651  * @author Ed Spencer
39652  *
39653  * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
39654  * data. Usually developers will not need to create or interact with proxies directly.
39655  *
39656  * # Types of Proxy
39657  *
39658  * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
39659  * The Client proxies save their data locally and include the following subclasses:
39660  *
39661  * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
39662  * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
39663  * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
39664  *
39665  * The Server proxies save their data by sending requests to some remote server. These proxies include:
39666  *
39667  * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
39668  * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
39669  * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
39670  *
39671  * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
39672  * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
39673  * respectively. Each Proxy subclass implements these functions.
39674  *
39675  * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
39676  * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
39677  * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
39678  * method also accepts a callback function to be called asynchronously on completion.
39679  *
39680  * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
39681  * method.
39682  */
39683 Ext.define('Ext.data.proxy.Proxy', {
39684     alias: 'proxy.proxy',
39685     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
39686     requires: [
39687         'Ext.data.reader.Json',
39688         'Ext.data.writer.Json'
39689     ],
39690     uses: [
39691         'Ext.data.Batch', 
39692         'Ext.data.Operation', 
39693         'Ext.data.Model'
39694     ],
39695     mixins: {
39696         observable: 'Ext.util.Observable'
39697     },
39698     
39699     /**
39700      * @cfg {String} batchOrder
39701      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
39702      * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
39703      */
39704     batchOrder: 'create,update,destroy',
39705     
39706     /**
39707      * @cfg {Boolean} batchActions
39708      * True to batch actions of a particular type when synchronizing the store. Defaults to true.
39709      */
39710     batchActions: true,
39711     
39712     /**
39713      * @cfg {String} defaultReaderType
39714      * The default registered reader type. Defaults to 'json'.
39715      * @private
39716      */
39717     defaultReaderType: 'json',
39718     
39719     /**
39720      * @cfg {String} defaultWriterType
39721      * The default registered writer type. Defaults to 'json'.
39722      * @private
39723      */
39724     defaultWriterType: 'json',
39725     
39726     /**
39727      * @cfg {String/Ext.data.Model} model
39728      * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
39729      * Model constructor. Required.
39730      */
39731     
39732     /**
39733      * @cfg {Object/String/Ext.data.reader.Reader} reader
39734      * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
39735      * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
39736      */
39737     
39738     /**
39739      * @cfg {Object/String/Ext.data.writer.Writer} writer
39740      * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
39741      * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
39742      */
39743     
39744     isProxy: true,
39745     
39746     /**
39747      * Creates the Proxy
39748      * @param {Object} config (optional) Config object.
39749      */
39750     constructor: function(config) {
39751         config = config || {};
39752         
39753         if (config.model === undefined) {
39754             delete config.model;
39755         }
39756
39757         this.mixins.observable.constructor.call(this, config);
39758         
39759         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
39760             this.setModel(this.model);
39761         }
39762     },
39763     
39764     /**
39765      * Sets the model associated with this proxy. This will only usually be called by a Store
39766      *
39767      * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
39768      * or a reference to the model's constructor
39769      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
39770      */
39771     setModel: function(model, setOnStore) {
39772         this.model = Ext.ModelManager.getModel(model);
39773         
39774         var reader = this.reader,
39775             writer = this.writer;
39776         
39777         this.setReader(reader);
39778         this.setWriter(writer);
39779         
39780         if (setOnStore && this.store) {
39781             this.store.setModel(this.model);
39782         }
39783     },
39784     
39785     /**
39786      * Returns the model attached to this Proxy
39787      * @return {Ext.data.Model} The model
39788      */
39789     getModel: function() {
39790         return this.model;
39791     },
39792     
39793     /**
39794      * Sets the Proxy's Reader by string, config object or Reader instance
39795      *
39796      * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
39797      * a configuration object or an Ext.data.reader.Reader instance
39798      * @return {Ext.data.reader.Reader} The attached Reader object
39799      */
39800     setReader: function(reader) {
39801         var me = this;
39802         
39803         if (reader === undefined || typeof reader == 'string') {
39804             reader = {
39805                 type: reader
39806             };
39807         }
39808
39809         if (reader.isReader) {
39810             reader.setModel(me.model);
39811         } else {
39812             Ext.applyIf(reader, {
39813                 proxy: me,
39814                 model: me.model,
39815                 type : me.defaultReaderType
39816             });
39817
39818             reader = Ext.createByAlias('reader.' + reader.type, reader);
39819         }
39820         
39821         me.reader = reader;
39822         return me.reader;
39823     },
39824     
39825     /**
39826      * Returns the reader currently attached to this proxy instance
39827      * @return {Ext.data.reader.Reader} The Reader instance
39828      */
39829     getReader: function() {
39830         return this.reader;
39831     },
39832     
39833     /**
39834      * Sets the Proxy's Writer by string, config object or Writer instance
39835      *
39836      * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
39837      * a configuration object or an Ext.data.writer.Writer instance
39838      * @return {Ext.data.writer.Writer} The attached Writer object
39839      */
39840     setWriter: function(writer) {
39841         if (writer === undefined || typeof writer == 'string') {
39842             writer = {
39843                 type: writer
39844             };
39845         }
39846
39847         if (!(writer instanceof Ext.data.writer.Writer)) {
39848             Ext.applyIf(writer, {
39849                 model: this.model,
39850                 type : this.defaultWriterType
39851             });
39852
39853             writer = Ext.createByAlias('writer.' + writer.type, writer);
39854         }
39855         
39856         this.writer = writer;
39857         
39858         return this.writer;
39859     },
39860     
39861     /**
39862      * Returns the writer currently attached to this proxy instance
39863      * @return {Ext.data.writer.Writer} The Writer instance
39864      */
39865     getWriter: function() {
39866         return this.writer;
39867     },
39868     
39869     /**
39870      * Performs the given create operation.
39871      * @param {Ext.data.Operation} operation The Operation to perform
39872      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39873      * @param {Object} scope Scope to execute the callback function in
39874      * @method
39875      */
39876     create: Ext.emptyFn,
39877     
39878     /**
39879      * Performs the given read operation.
39880      * @param {Ext.data.Operation} operation The Operation to perform
39881      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39882      * @param {Object} scope Scope to execute the callback function in
39883      * @method
39884      */
39885     read: Ext.emptyFn,
39886     
39887     /**
39888      * Performs the given update operation.
39889      * @param {Ext.data.Operation} operation The Operation to perform
39890      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39891      * @param {Object} scope Scope to execute the callback function in
39892      * @method
39893      */
39894     update: Ext.emptyFn,
39895     
39896     /**
39897      * Performs the given destroy operation.
39898      * @param {Ext.data.Operation} operation The Operation to perform
39899      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39900      * @param {Object} scope Scope to execute the callback function in
39901      * @method
39902      */
39903     destroy: Ext.emptyFn,
39904     
39905     /**
39906      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
39907      * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
39908      *
39909      *     myProxy.batch({
39910      *         create : [myModel1, myModel2],
39911      *         update : [myModel3],
39912      *         destroy: [myModel4, myModel5]
39913      *     });
39914      *
39915      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
39916      * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
39917      * saved but should now be destroyed.
39918      *
39919      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
39920      * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
39921      * see {@link Ext.data.Batch}
39922      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
39923      */
39924     batch: function(operations, listeners) {
39925         var me = this,
39926             batch = Ext.create('Ext.data.Batch', {
39927                 proxy: me,
39928                 listeners: listeners || {}
39929             }),
39930             useBatch = me.batchActions, 
39931             records;
39932         
39933         Ext.each(me.batchOrder.split(','), function(action) {
39934             records = operations[action];
39935             if (records) {
39936                 if (useBatch) {
39937                     batch.add(Ext.create('Ext.data.Operation', {
39938                         action: action,
39939                         records: records
39940                     }));
39941                 } else {
39942                     Ext.each(records, function(record){
39943                         batch.add(Ext.create('Ext.data.Operation', {
39944                             action : action, 
39945                             records: [record]
39946                         }));
39947                     });
39948                 }
39949             }
39950         }, me);
39951         
39952         batch.start();
39953         return batch;
39954     }
39955 }, function() {
39956     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
39957     
39958     //backwards compatibility
39959     Ext.data.DataProxy = this;
39960     // Ext.deprecate('platform', '2.0', function() {
39961     //     Ext.data.DataProxy = this;
39962     // }, this);
39963 });
39964
39965 /**
39966  * @author Ed Spencer
39967  *
39968  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
39969  * would not usually be used directly.
39970  *
39971  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
39972  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
39973  * an alias of AjaxProxy).
39974  * @private
39975  */
39976 Ext.define('Ext.data.proxy.Server', {
39977     extend: 'Ext.data.proxy.Proxy',
39978     alias : 'proxy.server',
39979     alternateClassName: 'Ext.data.ServerProxy',
39980     uses  : ['Ext.data.Request'],
39981
39982     /**
39983      * @cfg {String} url
39984      * The URL from which to request the data object.
39985      */
39986
39987     /**
39988      * @cfg {String} pageParam
39989      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
39990      * want to send a page parameter.
39991      */
39992     pageParam: 'page',
39993
39994     /**
39995      * @cfg {String} startParam
39996      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
39997      * want to send a start parameter.
39998      */
39999     startParam: 'start',
40000
40001     /**
40002      * @cfg {String} limitParam
40003      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
40004      * want to send a limit parameter.
40005      */
40006     limitParam: 'limit',
40007
40008     /**
40009      * @cfg {String} groupParam
40010      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
40011      * want to send a group parameter.
40012      */
40013     groupParam: 'group',
40014
40015     /**
40016      * @cfg {String} sortParam
40017      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
40018      * want to send a sort parameter.
40019      */
40020     sortParam: 'sort',
40021
40022     /**
40023      * @cfg {String} filterParam
40024      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
40025      * want to send a filter parameter.
40026      */
40027     filterParam: 'filter',
40028
40029     /**
40030      * @cfg {String} directionParam
40031      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
40032      * true.** Defaults to 'dir'.
40033      */
40034     directionParam: 'dir',
40035
40036     /**
40037      * @cfg {Boolean} simpleSortMode
40038      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
40039      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
40040      * or 'DESC'.
40041      */
40042     simpleSortMode: false,
40043
40044     /**
40045      * @cfg {Boolean} noCache
40046      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
40047      */
40048     noCache : true,
40049
40050     /**
40051      * @cfg {String} cacheString
40052      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
40053      */
40054     cacheString: "_dc",
40055
40056     /**
40057      * @cfg {Number} timeout
40058      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
40059      */
40060     timeout : 30000,
40061
40062     /**
40063      * @cfg {Object} api
40064      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
40065      *
40066      *     api: {
40067      *         create  : undefined,
40068      *         read    : undefined,
40069      *         update  : undefined,
40070      *         destroy : undefined
40071      *     }
40072      *
40073      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
40074      * {@link #api} property, or if undefined default to the configured
40075      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
40076      *
40077      * For example:
40078      *
40079      *     api: {
40080      *         create  : '/controller/new',
40081      *         read    : '/controller/load',
40082      *         update  : '/controller/update',
40083      *         destroy : '/controller/destroy_action'
40084      *     }
40085      *
40086      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
40087      * configured {@link Ext.data.proxy.Server#url url}.
40088      */
40089
40090     constructor: function(config) {
40091         var me = this;
40092
40093         config = config || {};
40094         this.addEvents(
40095             /**
40096              * @event exception
40097              * Fires when the server returns an exception
40098              * @param {Ext.data.proxy.Proxy} this
40099              * @param {Object} response The response from the AJAX request
40100              * @param {Ext.data.Operation} operation The operation that triggered request
40101              */
40102             'exception'
40103         );
40104         me.callParent([config]);
40105
40106         /**
40107          * @cfg {Object} extraParams
40108          * Extra parameters that will be included on every request. Individual requests with params of the same name
40109          * will override these params when they are in conflict.
40110          */
40111         me.extraParams = config.extraParams || {};
40112
40113         me.api = config.api || {};
40114
40115         //backwards compatibility, will be deprecated in 5.0
40116         me.nocache = me.noCache;
40117     },
40118
40119     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
40120     create: function() {
40121         return this.doRequest.apply(this, arguments);
40122     },
40123
40124     read: function() {
40125         return this.doRequest.apply(this, arguments);
40126     },
40127
40128     update: function() {
40129         return this.doRequest.apply(this, arguments);
40130     },
40131
40132     destroy: function() {
40133         return this.doRequest.apply(this, arguments);
40134     },
40135
40136     /**
40137      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
40138      * that this Proxy is attached to.
40139      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
40140      * @return {Ext.data.Request} The request object
40141      */
40142     buildRequest: function(operation) {
40143         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
40144             request;
40145
40146         //copy any sorters, filters etc into the params so they can be sent over the wire
40147         params = Ext.applyIf(params, this.getParams(operation));
40148
40149         if (operation.id && !params.id) {
40150             params.id = operation.id;
40151         }
40152
40153         request = Ext.create('Ext.data.Request', {
40154             params   : params,
40155             action   : operation.action,
40156             records  : operation.records,
40157             operation: operation,
40158             url      : operation.url
40159         });
40160
40161         request.url = this.buildUrl(request);
40162
40163         /*
40164          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
40165          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
40166          */
40167         operation.request = request;
40168
40169         return request;
40170     },
40171
40172     // Should this be documented as protected method?
40173     processResponse: function(success, operation, request, response, callback, scope){
40174         var me = this,
40175             reader,
40176             result;
40177
40178         if (success === true) {
40179             reader = me.getReader();
40180             result = reader.read(me.extractResponseData(response));
40181
40182             if (result.success !== false) {
40183                 //see comment in buildRequest for why we include the response object here
40184                 Ext.apply(operation, {
40185                     response: response,
40186                     resultSet: result
40187                 });
40188
40189                 operation.commitRecords(result.records);
40190                 operation.setCompleted();
40191                 operation.setSuccessful();
40192             } else {
40193                 operation.setException(result.message);
40194                 me.fireEvent('exception', this, response, operation);
40195             }
40196         } else {
40197             me.setException(operation, response);
40198             me.fireEvent('exception', this, response, operation);
40199         }
40200
40201         //this callback is the one that was passed to the 'read' or 'write' function above
40202         if (typeof callback == 'function') {
40203             callback.call(scope || me, operation);
40204         }
40205
40206         me.afterRequest(request, success);
40207     },
40208
40209     /**
40210      * Sets up an exception on the operation
40211      * @private
40212      * @param {Ext.data.Operation} operation The operation
40213      * @param {Object} response The response
40214      */
40215     setException: function(operation, response){
40216         operation.setException({
40217             status: response.status,
40218             statusText: response.statusText
40219         });
40220     },
40221
40222     /**
40223      * Template method to allow subclasses to specify how to get the response for the reader.
40224      * @template
40225      * @private
40226      * @param {Object} response The server response
40227      * @return {Object} The response data to be used by the reader
40228      */
40229     extractResponseData: function(response){
40230         return response;
40231     },
40232
40233     /**
40234      * Encode any values being sent to the server. Can be overridden in subclasses.
40235      * @private
40236      * @param {Array} An array of sorters/filters.
40237      * @return {Object} The encoded value
40238      */
40239     applyEncoding: function(value){
40240         return Ext.encode(value);
40241     },
40242
40243     /**
40244      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
40245      * this simply JSON-encodes the sorter data
40246      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
40247      * @return {String} The encoded sorters
40248      */
40249     encodeSorters: function(sorters) {
40250         var min = [],
40251             length = sorters.length,
40252             i = 0;
40253
40254         for (; i < length; i++) {
40255             min[i] = {
40256                 property : sorters[i].property,
40257                 direction: sorters[i].direction
40258             };
40259         }
40260         return this.applyEncoding(min);
40261
40262     },
40263
40264     /**
40265      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
40266      * this simply JSON-encodes the filter data
40267      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
40268      * @return {String} The encoded filters
40269      */
40270     encodeFilters: function(filters) {
40271         var min = [],
40272             length = filters.length,
40273             i = 0;
40274
40275         for (; i < length; i++) {
40276             min[i] = {
40277                 property: filters[i].property,
40278                 value   : filters[i].value
40279             };
40280         }
40281         return this.applyEncoding(min);
40282     },
40283
40284     /**
40285      * @private
40286      * Copy any sorters, filters etc into the params so they can be sent over the wire
40287      */
40288     getParams: function(operation) {
40289         var me             = this,
40290             params         = {},
40291             isDef          = Ext.isDefined,
40292             groupers       = operation.groupers,
40293             sorters        = operation.sorters,
40294             filters        = operation.filters,
40295             page           = operation.page,
40296             start          = operation.start,
40297             limit          = operation.limit,
40298
40299             simpleSortMode = me.simpleSortMode,
40300
40301             pageParam      = me.pageParam,
40302             startParam     = me.startParam,
40303             limitParam     = me.limitParam,
40304             groupParam     = me.groupParam,
40305             sortParam      = me.sortParam,
40306             filterParam    = me.filterParam,
40307             directionParam = me.directionParam;
40308
40309         if (pageParam && isDef(page)) {
40310             params[pageParam] = page;
40311         }
40312
40313         if (startParam && isDef(start)) {
40314             params[startParam] = start;
40315         }
40316
40317         if (limitParam && isDef(limit)) {
40318             params[limitParam] = limit;
40319         }
40320
40321         if (groupParam && groupers && groupers.length > 0) {
40322             // Grouper is a subclass of sorter, so we can just use the sorter method
40323             params[groupParam] = me.encodeSorters(groupers);
40324         }
40325
40326         if (sortParam && sorters && sorters.length > 0) {
40327             if (simpleSortMode) {
40328                 params[sortParam] = sorters[0].property;
40329                 params[directionParam] = sorters[0].direction;
40330             } else {
40331                 params[sortParam] = me.encodeSorters(sorters);
40332             }
40333
40334         }
40335
40336         if (filterParam && filters && filters.length > 0) {
40337             params[filterParam] = me.encodeFilters(filters);
40338         }
40339
40340         return params;
40341     },
40342
40343     /**
40344      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
40345      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
40346      * @param {Ext.data.Request} request The request object
40347      * @return {String} The url
40348      */
40349     buildUrl: function(request) {
40350         var me = this,
40351             url = me.getUrl(request);
40352
40353         if (!url) {
40354             Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
40355         }
40356
40357         if (me.noCache) {
40358             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
40359         }
40360
40361         return url;
40362     },
40363
40364     /**
40365      * Get the url for the request taking into account the order of priority,
40366      * - The request
40367      * - The api
40368      * - The url
40369      * @private
40370      * @param {Ext.data.Request} request The request
40371      * @return {String} The url
40372      */
40373     getUrl: function(request){
40374         return request.url || this.api[request.action] || this.url;
40375     },
40376
40377     /**
40378      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
40379      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
40380      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
40381      * each of the methods that delegate to it.
40382      *
40383      * @param {Ext.data.Operation} operation The Ext.data.Operation object
40384      * @param {Function} callback The callback function to call when the Operation has completed
40385      * @param {Object} scope The scope in which to execute the callback
40386      */
40387     doRequest: function(operation, callback, scope) {
40388         Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
40389     },
40390
40391     /**
40392      * Optional callback function which can be used to clean up after a request has been completed.
40393      * @param {Ext.data.Request} request The Request object
40394      * @param {Boolean} success True if the request was successful
40395      * @method
40396      */
40397     afterRequest: Ext.emptyFn,
40398
40399     onDestroy: function() {
40400         Ext.destroy(this.reader, this.writer);
40401     }
40402 });
40403
40404 /**
40405  * @author Ed Spencer
40406  *
40407  * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
40408  * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
40409  * Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a {@link Ext.data.Model
40410  * Model}:
40411  *
40412  *     Ext.define('User', {
40413  *         extend: 'Ext.data.Model',
40414  *         fields: ['id', 'name', 'email']
40415  *     });
40416  *
40417  *     //The Store contains the AjaxProxy as an inline configuration
40418  *     var store = Ext.create('Ext.data.Store', {
40419  *         model: 'User',
40420  *         proxy: {
40421  *             type: 'ajax',
40422  *             url : 'users.json'
40423  *         }
40424  *     });
40425  *
40426  *     store.load();
40427  *
40428  * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
40429  * the fields that we expect the server to return. Next we set up the Store itself, along with a
40430  * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
40431  * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
40432  * It's as if we'd done this:
40433  *
40434  *     new Ext.data.proxy.Ajax({
40435  *         url: 'users.json',
40436  *         model: 'User',
40437  *         reader: 'json'
40438  *     });
40439  *
40440  * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
40441  * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
40442  * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
40443  *
40444  * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
40445  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
40446  * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
40447  * will be sent as a POST request).
40448  *
40449  * # Limitations
40450  *
40451  * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
40452  * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
40453  * talking to each other via AJAX.
40454  *
40455  * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
40456  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
40457  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
40458  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
40459  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
40460  *
40461  * # Readers and Writers
40462  *
40463  * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
40464  * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
40465  * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
40466  * Ext.data.reader.Reader Reader} instance:
40467  *
40468  *     var proxy = new Ext.data.proxy.Ajax({
40469  *         model: 'User',
40470  *         reader: {
40471  *             type: 'xml',
40472  *             root: 'users'
40473  *         }
40474  *     });
40475  *
40476  *     proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
40477  *
40478  * # Url generation
40479  *
40480  * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
40481  * each request. These are controlled with the following configuration options:
40482  *
40483  * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
40484  * - {@link #sortParam} - controls how sort information is sent to the server
40485  * - {@link #groupParam} - controls how grouping information is sent to the server
40486  * - {@link #filterParam} - controls how filter information is sent to the server
40487  *
40488  * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
40489  * the generated urls, let's say we're loading the Proxy with the following Operation:
40490  *
40491  *     var operation = new Ext.data.Operation({
40492  *         action: 'read',
40493  *         page  : 2
40494  *     });
40495  *
40496  * Now we'll issue the request for this Operation by calling {@link #read}:
40497  *
40498  *     var proxy = new Ext.data.proxy.Ajax({
40499  *         url: '/users'
40500  *     });
40501  *
40502  *     proxy.read(operation); //GET /users?page=2
40503  *
40504  * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
40505  * to the server:
40506  *
40507  *     var proxy = new Ext.data.proxy.Ajax({
40508  *         url: '/users',
40509  *         pagePage: 'pageNumber'
40510  *     });
40511  *
40512  *     proxy.read(operation); //GET /users?pageNumber=2
40513  *
40514  * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
40515  *
40516  *     var operation = new Ext.data.Operation({
40517  *         action: 'read',
40518  *         start : 50,
40519  *         limit : 25
40520  *     });
40521  *
40522  *     var proxy = new Ext.data.proxy.Ajax({
40523  *         url: '/users'
40524  *     });
40525  *
40526  *     proxy.read(operation); //GET /users?start=50&limit;=25
40527  *
40528  * Again we can customize this url:
40529  *
40530  *     var proxy = new Ext.data.proxy.Ajax({
40531  *         url: '/users',
40532  *         startParam: 'startIndex',
40533  *         limitParam: 'limitIndex'
40534  *     });
40535  *
40536  *     proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
40537  *
40538  * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
40539  * expressive Operation object:
40540  *
40541  *     var operation = new Ext.data.Operation({
40542  *         action: 'read',
40543  *         sorters: [
40544  *             new Ext.util.Sorter({
40545  *                 property : 'name',
40546  *                 direction: 'ASC'
40547  *             }),
40548  *             new Ext.util.Sorter({
40549  *                 property : 'age',
40550  *                 direction: 'DESC'
40551  *             })
40552  *         ],
40553  *         filters: [
40554  *             new Ext.util.Filter({
40555  *                 property: 'eyeColor',
40556  *                 value   : 'brown'
40557  *             })
40558  *         ]
40559  *     });
40560  *
40561  * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
40562  * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
40563  * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
40564  *
40565  *     var proxy = new Ext.data.proxy.Ajax({
40566  *         url: '/users'
40567  *     });
40568  *
40569  *     proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
40570  *
40571  * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
40572  * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
40573  * that format like this:
40574  *
40575  *      var proxy = new Ext.data.proxy.Ajax({
40576  *          url: '/users',
40577  *          sortParam: 'sortBy',
40578  *          filterParam: 'filterBy',
40579  *
40580  *          //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
40581  *          encodeSorters: function(sorters) {
40582  *              var length   = sorters.length,
40583  *                  sortStrs = [],
40584  *                  sorter, i;
40585  *
40586  *              for (i = 0; i < length; i++) {
40587  *                  sorter = sorters[i];
40588  *
40589  *                  sortStrs[i] = sorter.property + '#' + sorter.direction
40590  *              }
40591  *
40592  *              return sortStrs.join(",");
40593  *          }
40594  *      });
40595  *
40596  *      proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
40597  *
40598  * We can also provide a custom {@link #encodeFilters} function to encode our filters.
40599  *
40600  * @constructor
40601  * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
40602  * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
40603  * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
40604  *
40605  * @param {Object} config (optional) Config object.
40606  * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
40607  */
40608 Ext.define('Ext.data.proxy.Ajax', {
40609     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
40610     extend: 'Ext.data.proxy.Server',
40611     alias: 'proxy.ajax',
40612     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
40613     
40614     /**
40615      * @property {Object} actionMethods
40616      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
40617      * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
40618      * correct RESTful methods.
40619      */
40620     actionMethods: {
40621         create : 'POST',
40622         read   : 'GET',
40623         update : 'POST',
40624         destroy: 'POST'
40625     },
40626     
40627     /**
40628      * @cfg {Object} headers
40629      * Any headers to add to the Ajax request. Defaults to undefined.
40630      */
40631     
40632     /**
40633      * @ignore
40634      */
40635     doRequest: function(operation, callback, scope) {
40636         var writer  = this.getWriter(),
40637             request = this.buildRequest(operation, callback, scope);
40638             
40639         if (operation.allowWrite()) {
40640             request = writer.write(request);
40641         }
40642         
40643         Ext.apply(request, {
40644             headers       : this.headers,
40645             timeout       : this.timeout,
40646             scope         : this,
40647             callback      : this.createRequestCallback(request, operation, callback, scope),
40648             method        : this.getMethod(request),
40649             disableCaching: false // explicitly set it to false, ServerProxy handles caching
40650         });
40651         
40652         Ext.Ajax.request(request);
40653         
40654         return request;
40655     },
40656     
40657     /**
40658      * Returns the HTTP method name for a given request. By default this returns based on a lookup on
40659      * {@link #actionMethods}.
40660      * @param {Ext.data.Request} request The request object
40661      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
40662      */
40663     getMethod: function(request) {
40664         return this.actionMethods[request.action];
40665     },
40666     
40667     /**
40668      * @private
40669      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
40670      * of code duplication inside the returned function so we need to find a way to DRY this up.
40671      * @param {Ext.data.Request} request The Request object
40672      * @param {Ext.data.Operation} operation The Operation being executed
40673      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
40674      * passed to doRequest
40675      * @param {Object} scope The scope in which to execute the callback function
40676      * @return {Function} The callback function
40677      */
40678     createRequestCallback: function(request, operation, callback, scope) {
40679         var me = this;
40680         
40681         return function(options, success, response) {
40682             me.processResponse(success, operation, request, response, callback, scope);
40683         };
40684     }
40685 }, function() {
40686     //backwards compatibility, remove in Ext JS 5.0
40687     Ext.data.HttpProxy = this;
40688 });
40689
40690 /**
40691  * @author Ed Spencer
40692  *
40693  * A Model represents some object that your application manages. For example, one might define a Model for Users,
40694  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
40695  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
40696  * of the data-bound components in Ext.
40697  *
40698  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
40699  *
40700  *     Ext.define('User', {
40701  *         extend: 'Ext.data.Model',
40702  *         fields: [
40703  *             {name: 'name',  type: 'string'},
40704  *             {name: 'age',   type: 'int'},
40705  *             {name: 'phone', type: 'string'},
40706  *             {name: 'alive', type: 'boolean', defaultValue: true}
40707  *         ],
40708  *
40709  *         changeName: function() {
40710  *             var oldName = this.get('name'),
40711  *                 newName = oldName + " The Barbarian";
40712  *
40713  *             this.set('name', newName);
40714  *         }
40715  *     });
40716  *
40717  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
40718  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
40719  *
40720  * Now we can create instances of our User model and call any model logic we defined:
40721  *
40722  *     var user = Ext.create('User', {
40723  *         name : 'Conan',
40724  *         age  : 24,
40725  *         phone: '555-555-5555'
40726  *     });
40727  *
40728  *     user.changeName();
40729  *     user.get('name'); //returns "Conan The Barbarian"
40730  *
40731  * # Validations
40732  *
40733  * Models have built-in support for validations, which are executed against the validator functions in {@link
40734  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
40735  * models:
40736  *
40737  *     Ext.define('User', {
40738  *         extend: 'Ext.data.Model',
40739  *         fields: [
40740  *             {name: 'name',     type: 'string'},
40741  *             {name: 'age',      type: 'int'},
40742  *             {name: 'phone',    type: 'string'},
40743  *             {name: 'gender',   type: 'string'},
40744  *             {name: 'username', type: 'string'},
40745  *             {name: 'alive',    type: 'boolean', defaultValue: true}
40746  *         ],
40747  *
40748  *         validations: [
40749  *             {type: 'presence',  field: 'age'},
40750  *             {type: 'length',    field: 'name',     min: 2},
40751  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
40752  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
40753  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
40754  *         ]
40755  *     });
40756  *
40757  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
40758  * object:
40759  *
40760  *     var instance = Ext.create('User', {
40761  *         name: 'Ed',
40762  *         gender: 'Male',
40763  *         username: 'edspencer'
40764  *     });
40765  *
40766  *     var errors = instance.validate();
40767  *
40768  * # Associations
40769  *
40770  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
40771  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
40772  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
40773  * this:
40774  *
40775  *     Ext.define('Post', {
40776  *         extend: 'Ext.data.Model',
40777  *         fields: ['id', 'user_id'],
40778  *
40779  *         belongsTo: 'User',
40780  *         hasMany  : {model: 'Comment', name: 'comments'}
40781  *     });
40782  *
40783  *     Ext.define('Comment', {
40784  *         extend: 'Ext.data.Model',
40785  *         fields: ['id', 'user_id', 'post_id'],
40786  *
40787  *         belongsTo: 'Post'
40788  *     });
40789  *
40790  *     Ext.define('User', {
40791  *         extend: 'Ext.data.Model',
40792  *         fields: ['id'],
40793  *
40794  *         hasMany: [
40795  *             'Post',
40796  *             {model: 'Comment', name: 'comments'}
40797  *         ]
40798  *     });
40799  *
40800  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
40801  * usage and configuration of associations. Note that associations can also be specified like this:
40802  *
40803  *     Ext.define('User', {
40804  *         extend: 'Ext.data.Model',
40805  *         fields: ['id'],
40806  *
40807  *         associations: [
40808  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
40809  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
40810  *         ]
40811  *     });
40812  *
40813  * # Using a Proxy
40814  *
40815  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
40816  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
40817  * can be set directly on the Model:
40818  *
40819  *     Ext.define('User', {
40820  *         extend: 'Ext.data.Model',
40821  *         fields: ['id', 'name', 'email'],
40822  *
40823  *         proxy: {
40824  *             type: 'rest',
40825  *             url : '/users'
40826  *         }
40827  *     });
40828  *
40829  * 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
40830  * RESTful backend. Let's see how this works:
40831  *
40832  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
40833  *
40834  *     user.save(); //POST /users
40835  *
40836  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
40837  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
40838  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
40839  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
40840  *
40841  * Loading data via the Proxy is equally easy:
40842  *
40843  *     //get a reference to the User model class
40844  *     var User = Ext.ModelManager.getModel('User');
40845  *
40846  *     //Uses the configured RestProxy to make a GET request to /users/123
40847  *     User.load(123, {
40848  *         success: function(user) {
40849  *             console.log(user.getId()); //logs 123
40850  *         }
40851  *     });
40852  *
40853  * Models can also be updated and destroyed easily:
40854  *
40855  *     //the user Model we loaded in the last snippet:
40856  *     user.set('name', 'Edward Spencer');
40857  *
40858  *     //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
40859  *     user.save({
40860  *         success: function() {
40861  *             console.log('The User was updated');
40862  *         }
40863  *     });
40864  *
40865  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
40866  *     user.destroy({
40867  *         success: function() {
40868  *             console.log('The User was destroyed!');
40869  *         }
40870  *     });
40871  *
40872  * # Usage in Stores
40873  *
40874  * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
40875  * creating a {@link Ext.data.Store Store}:
40876  *
40877  *     var store = Ext.create('Ext.data.Store', {
40878  *         model: 'User'
40879  *     });
40880  *
40881  *     //uses the Proxy we set up on Model to load the Store data
40882  *     store.load();
40883  *
40884  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
40885  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
40886  * Ext.data.Store Store docs} for more information on Stores.
40887  *
40888  * @constructor
40889  * Creates new Model instance.
40890  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
40891  * @param {Number} id (optional) Unique ID to assign to this model instance
40892  */
40893 Ext.define('Ext.data.Model', {
40894     alternateClassName: 'Ext.data.Record',
40895
40896     mixins: {
40897         observable: 'Ext.util.Observable'
40898     },
40899
40900     requires: [
40901         'Ext.ModelManager',
40902         'Ext.data.IdGenerator',
40903         'Ext.data.Field',
40904         'Ext.data.Errors',
40905         'Ext.data.Operation',
40906         'Ext.data.validations',
40907         'Ext.data.proxy.Ajax',
40908         'Ext.util.MixedCollection'
40909     ],
40910
40911     onClassExtended: function(cls, data) {
40912         var onBeforeClassCreated = data.onBeforeClassCreated;
40913
40914         data.onBeforeClassCreated = function(cls, data) {
40915             var me = this,
40916                 name = Ext.getClassName(cls),
40917                 prototype = cls.prototype,
40918                 superCls = cls.prototype.superclass,
40919
40920                 validations = data.validations || [],
40921                 fields = data.fields || [],
40922                 associations = data.associations || [],
40923                 belongsTo = data.belongsTo,
40924                 hasMany = data.hasMany,
40925                 idgen = data.idgen,
40926
40927                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
40928                     return field.name;
40929                 }),
40930
40931                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
40932                     return association.name;
40933                 }),
40934
40935                 superValidations = superCls.validations,
40936                 superFields = superCls.fields,
40937                 superAssociations = superCls.associations,
40938
40939                 association, i, ln,
40940                 dependencies = [];
40941
40942             // Save modelName on class and its prototype
40943             cls.modelName = name;
40944             prototype.modelName = name;
40945
40946             // Merge the validations of the superclass and the new subclass
40947             if (superValidations) {
40948                 validations = superValidations.concat(validations);
40949             }
40950
40951             data.validations = validations;
40952
40953             // Merge the fields of the superclass and the new subclass
40954             if (superFields) {
40955                 fields = superFields.items.concat(fields);
40956             }
40957
40958             for (i = 0, ln = fields.length; i < ln; ++i) {
40959                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
40960             }
40961
40962             data.fields = fieldsMixedCollection;
40963
40964             if (idgen) {
40965                 data.idgen = Ext.data.IdGenerator.get(idgen);
40966             }
40967
40968             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
40969             //we support that here
40970             if (belongsTo) {
40971                 belongsTo = Ext.Array.from(belongsTo);
40972
40973                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
40974                     association = belongsTo[i];
40975
40976                     if (!Ext.isObject(association)) {
40977                         association = {model: association};
40978                     }
40979
40980                     association.type = 'belongsTo';
40981                     associations.push(association);
40982                 }
40983
40984                 delete data.belongsTo;
40985             }
40986
40987             if (hasMany) {
40988                 hasMany = Ext.Array.from(hasMany);
40989                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
40990                     association = hasMany[i];
40991
40992                     if (!Ext.isObject(association)) {
40993                         association = {model: association};
40994                     }
40995
40996                     association.type = 'hasMany';
40997                     associations.push(association);
40998                 }
40999
41000                 delete data.hasMany;
41001             }
41002
41003             if (superAssociations) {
41004                 associations = superAssociations.items.concat(associations);
41005             }
41006
41007             for (i = 0, ln = associations.length; i < ln; ++i) {
41008                 dependencies.push('association.' + associations[i].type.toLowerCase());
41009             }
41010
41011             if (data.proxy) {
41012                 if (typeof data.proxy === 'string') {
41013                     dependencies.push('proxy.' + data.proxy);
41014                 }
41015                 else if (typeof data.proxy.type === 'string') {
41016                     dependencies.push('proxy.' + data.proxy.type);
41017                 }
41018             }
41019
41020             Ext.require(dependencies, function() {
41021                 Ext.ModelManager.registerType(name, cls);
41022
41023                 for (i = 0, ln = associations.length; i < ln; ++i) {
41024                     association = associations[i];
41025
41026                     Ext.apply(association, {
41027                         ownerModel: name,
41028                         associatedModel: association.model
41029                     });
41030
41031                     if (Ext.ModelManager.getModel(association.model) === undefined) {
41032                         Ext.ModelManager.registerDeferredAssociation(association);
41033                     } else {
41034                         associationsMixedCollection.add(Ext.data.Association.create(association));
41035                     }
41036                 }
41037
41038                 data.associations = associationsMixedCollection;
41039
41040                 onBeforeClassCreated.call(me, cls, data);
41041
41042                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
41043
41044                 // Fire the onModelDefined template method on ModelManager
41045                 Ext.ModelManager.onModelDefined(cls);
41046             });
41047         };
41048     },
41049
41050     inheritableStatics: {
41051         /**
41052          * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41053          * {@link Ext#createByAlias Ext.createByAlias}.
41054          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41055          * @return {Ext.data.proxy.Proxy}
41056          * @static
41057          * @inheritable
41058          */
41059         setProxy: function(proxy) {
41060             //make sure we have an Ext.data.proxy.Proxy object
41061             if (!proxy.isProxy) {
41062                 if (typeof proxy == "string") {
41063                     proxy = {
41064                         type: proxy
41065                     };
41066                 }
41067                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41068             }
41069             proxy.setModel(this);
41070             this.proxy = this.prototype.proxy = proxy;
41071
41072             return proxy;
41073         },
41074
41075         /**
41076          * Returns the configured Proxy for this Model
41077          * @return {Ext.data.proxy.Proxy} The proxy
41078          * @static
41079          * @inheritable
41080          */
41081         getProxy: function() {
41082             return this.proxy;
41083         },
41084
41085         /**
41086          * Asynchronously loads a model instance by id. Sample usage:
41087          *
41088          *     MyApp.User = Ext.define('User', {
41089          *         extend: 'Ext.data.Model',
41090          *         fields: [
41091          *             {name: 'id', type: 'int'},
41092          *             {name: 'name', type: 'string'}
41093          *         ]
41094          *     });
41095          *
41096          *     MyApp.User.load(10, {
41097          *         scope: this,
41098          *         failure: function(record, operation) {
41099          *             //do something if the load failed
41100          *         },
41101          *         success: function(record, operation) {
41102          *             //do something if the load succeeded
41103          *         },
41104          *         callback: function(record, operation) {
41105          *             //do something whether the load succeeded or failed
41106          *         }
41107          *     });
41108          *
41109          * @param {Number} id The id of the model to load
41110          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
41111          * optional scope
41112          * @static
41113          * @inheritable
41114          */
41115         load: function(id, config) {
41116             config = Ext.apply({}, config);
41117             config = Ext.applyIf(config, {
41118                 action: 'read',
41119                 id    : id
41120             });
41121
41122             var operation  = Ext.create('Ext.data.Operation', config),
41123                 scope      = config.scope || this,
41124                 record     = null,
41125                 callback;
41126
41127             callback = function(operation) {
41128                 if (operation.wasSuccessful()) {
41129                     record = operation.getRecords()[0];
41130                     Ext.callback(config.success, scope, [record, operation]);
41131                 } else {
41132                     Ext.callback(config.failure, scope, [record, operation]);
41133                 }
41134                 Ext.callback(config.callback, scope, [record, operation]);
41135             };
41136
41137             this.proxy.read(operation, callback, this);
41138         }
41139     },
41140
41141     statics: {
41142         PREFIX : 'ext-record',
41143         AUTO_ID: 1,
41144         EDIT   : 'edit',
41145         REJECT : 'reject',
41146         COMMIT : 'commit',
41147
41148         /**
41149          * Generates a sequential id. This method is typically called when a record is {@link Ext#create
41150          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
41151          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
41152          *
41153          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
41154          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
41155          *
41156          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
41157          * @return {String} auto-generated string id, `"ext-record-i++"`;
41158          * @static
41159          */
41160         id: function(rec) {
41161             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
41162             rec.phantom = true;
41163             rec.internalId = id;
41164             return id;
41165         }
41166     },
41167
41168     /**
41169      * @cfg {String/Object} idgen
41170      * The id generator to use for this model. The default id generator does not generate
41171      * values for the {@link #idProperty}.
41172      *
41173      * This can be overridden at the model level to provide a custom generator for a model.
41174      * The simplest form of this would be:
41175      *
41176      *      Ext.define('MyApp.data.MyModel', {
41177      *          extend: 'Ext.data.Model',
41178      *          requires: ['Ext.data.SequentialIdGenerator'],
41179      *          idgen: 'sequential',
41180      *          ...
41181      *      });
41182      *
41183      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
41184      * as 1, 2, 3 etc..
41185      *
41186      * Another useful id generator is {@link Ext.data.UuidGenerator}:
41187      *
41188      *      Ext.define('MyApp.data.MyModel', {
41189      *          extend: 'Ext.data.Model',
41190      *          requires: ['Ext.data.UuidGenerator'],
41191      *          idgen: 'uuid',
41192      *          ...
41193      *      });
41194      *
41195      * An id generation can also be further configured:
41196      *
41197      *      Ext.define('MyApp.data.MyModel', {
41198      *          extend: 'Ext.data.Model',
41199      *          idgen: {
41200      *              type: 'sequential',
41201      *              seed: 1000,
41202      *              prefix: 'ID_'
41203      *          }
41204      *      });
41205      *
41206      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
41207      *
41208      * If multiple models share an id space, a single generator can be shared:
41209      *
41210      *      Ext.define('MyApp.data.MyModelX', {
41211      *          extend: 'Ext.data.Model',
41212      *          idgen: {
41213      *              type: 'sequential',
41214      *              id: 'xy'
41215      *          }
41216      *      });
41217      *
41218      *      Ext.define('MyApp.data.MyModelY', {
41219      *          extend: 'Ext.data.Model',
41220      *          idgen: {
41221      *              type: 'sequential',
41222      *              id: 'xy'
41223      *          }
41224      *      });
41225      *
41226      * For more complex, shared id generators, a custom generator is the best approach.
41227      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
41228      *
41229      * @markdown
41230      */
41231     idgen: {
41232         isGenerator: true,
41233         type: 'default',
41234
41235         generate: function () {
41236             return null;
41237         },
41238         getRecId: function (rec) {
41239             return rec.modelName + '-' + rec.internalId;
41240         }
41241     },
41242
41243     /**
41244      * @property {Boolean} editing
41245      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
41246      */
41247     editing : false,
41248
41249     /**
41250      * @property {Boolean} dirty
41251      * True if this Record has been modified. Read-only.
41252      */
41253     dirty : false,
41254
41255     /**
41256      * @cfg {String} persistenceProperty
41257      * The property on this Persistable object that its data is saved to. Defaults to 'data'
41258      * (e.g. all persistable data resides in this.data.)
41259      */
41260     persistenceProperty: 'data',
41261
41262     evented: false,
41263     isModel: true,
41264
41265     /**
41266      * @property {Boolean} phantom
41267      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
41268      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
41269      */
41270     phantom : false,
41271
41272     /**
41273      * @cfg {String} idProperty
41274      * The name of the field treated as this Model's unique id. Defaults to 'id'.
41275      */
41276     idProperty: 'id',
41277
41278     /**
41279      * @cfg {String} defaultProxyType
41280      * The string type of the default Model Proxy. Defaults to 'ajax'.
41281      */
41282     defaultProxyType: 'ajax',
41283
41284     // Fields config and property
41285     /**
41286      * @cfg {Object[]/String[]} fields
41287      * The fields for this model.
41288      */
41289     /**
41290      * @property {Ext.util.MixedCollection} fields
41291      * The fields defined on this model.
41292      */
41293
41294     /**
41295      * @cfg {Object[]} validations
41296      * An array of {@link Ext.data.validations validations} for this model.
41297      */
41298
41299     // Associations configs and properties
41300     /**
41301      * @cfg {Object[]} associations
41302      * An array of {@link Ext.data.Association associations} for this model.
41303      */
41304     /**
41305      * @cfg {String/Object/String[]/Object[]} hasMany
41306      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
41307      */
41308     /**
41309      * @cfg {String/Object/String[]/Object[]} belongsTo
41310      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
41311      */
41312     /**
41313      * @property {Ext.util.MixedCollection} associations
41314      * {@link Ext.data.Association Associations} defined on this model.
41315      */
41316
41317     /**
41318      * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
41319      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
41320      */
41321
41322     // raw not documented intentionally, meant to be used internally.
41323     constructor: function(data, id, raw) {
41324         data = data || {};
41325
41326         var me = this,
41327             fields,
41328             length,
41329             field,
41330             name,
41331             i,
41332             newId,
41333             isArray = Ext.isArray(data),
41334             newData = isArray ? {} : null; // to hold mapped array data if needed
41335
41336         /**
41337          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
41338          * @property internalId
41339          * @type String
41340          * @private
41341          */
41342         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
41343
41344         /**
41345          * @property {Object} raw The raw data used to create this model if created via a reader.
41346          */
41347         me.raw = raw;
41348
41349         Ext.applyIf(me, {
41350             data: {}
41351         });
41352
41353         /**
41354          * @property {Object} modified Key: value pairs of all fields whose values have changed
41355          */
41356         me.modified = {};
41357
41358         // Deal with spelling error in previous releases
41359         if (me.persistanceProperty) {
41360             if (Ext.isDefined(Ext.global.console)) {
41361                 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
41362             }
41363             me.persistenceProperty = me.persistanceProperty;
41364         }
41365         me[me.persistenceProperty] = {};
41366
41367         me.mixins.observable.constructor.call(me);
41368
41369         //add default field values if present
41370         fields = me.fields.items;
41371         length = fields.length;
41372
41373         for (i = 0; i < length; i++) {
41374             field = fields[i];
41375             name  = field.name;
41376
41377             if (isArray){
41378                 // Have to map array data so the values get assigned to the named fields
41379                 // rather than getting set as the field names with undefined values.
41380                 newData[name] = data[i];
41381             }
41382             else if (data[name] === undefined) {
41383                 data[name] = field.defaultValue;
41384             }
41385         }
41386
41387         me.set(newData || data);
41388
41389         if (me.getId()) {
41390             me.phantom = false;
41391         } else if (me.phantom) {
41392             newId = me.idgen.generate();
41393             if (newId !== null) {
41394                 me.setId(newId);
41395             }
41396         }
41397
41398         // clear any dirty/modified since we're initializing
41399         me.dirty = false;
41400         me.modified = {};
41401
41402         if (typeof me.init == 'function') {
41403             me.init();
41404         }
41405
41406         me.id = me.idgen.getRecId(me);
41407     },
41408
41409     /**
41410      * Returns the value of the given field
41411      * @param {String} fieldName The field to fetch the value for
41412      * @return {Object} The value
41413      */
41414     get: function(field) {
41415         return this[this.persistenceProperty][field];
41416     },
41417
41418     /**
41419      * Sets the given field to the given value, marks the instance as dirty
41420      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
41421      * @param {Object} value The value to set
41422      */
41423     set: function(fieldName, value) {
41424         var me = this,
41425             fields = me.fields,
41426             modified = me.modified,
41427             convertFields = [],
41428             field, key, i, currentValue, notEditing, count, length;
41429
41430         /*
41431          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
41432          * set those last so that all other possible data is set before the convert function is called
41433          */
41434         if (arguments.length == 1 && Ext.isObject(fieldName)) {
41435             notEditing = !me.editing;
41436             count = 0;
41437             for (key in fieldName) {
41438                 if (fieldName.hasOwnProperty(key)) {
41439
41440                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
41441                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
41442                     field = fields.get(key);
41443                     if (field && field.convert !== field.type.convert) {
41444                         convertFields.push(key);
41445                         continue;
41446                     }
41447
41448                     if (!count && notEditing) {
41449                         me.beginEdit();
41450                     }
41451                     ++count;
41452                     me.set(key, fieldName[key]);
41453                 }
41454             }
41455
41456             length = convertFields.length;
41457             if (length) {
41458                 if (!count && notEditing) {
41459                     me.beginEdit();
41460                 }
41461                 count += length;
41462                 for (i = 0; i < length; i++) {
41463                     field = convertFields[i];
41464                     me.set(field, fieldName[field]);
41465                 }
41466             }
41467
41468             if (notEditing && count) {
41469                 me.endEdit();
41470             }
41471         } else {
41472             if (fields) {
41473                 field = fields.get(fieldName);
41474
41475                 if (field && field.convert) {
41476                     value = field.convert(value, me);
41477                 }
41478             }
41479             currentValue = me.get(fieldName);
41480             me[me.persistenceProperty][fieldName] = value;
41481
41482             if (field && field.persist && !me.isEqual(currentValue, value)) {
41483                 if (me.isModified(fieldName)) {
41484                     if (me.isEqual(modified[fieldName], value)) {
41485                         // the original value in me.modified equals the new value, so the
41486                         // field is no longer modified
41487                         delete modified[fieldName];
41488                         // we might have removed the last modified field, so check to see if
41489                         // there are any modified fields remaining and correct me.dirty:
41490                         me.dirty = false;
41491                         for (key in modified) {
41492                             if (modified.hasOwnProperty(key)){
41493                                 me.dirty = true;
41494                                 break;
41495                             }
41496                         }
41497                     }
41498                 } else {
41499                     me.dirty = true;
41500                     modified[fieldName] = currentValue;
41501                 }
41502             }
41503
41504             if (!me.editing) {
41505                 me.afterEdit();
41506             }
41507         }
41508     },
41509
41510     /**
41511      * Checks if two values are equal, taking into account certain
41512      * special factors, for example dates.
41513      * @private
41514      * @param {Object} a The first value
41515      * @param {Object} b The second value
41516      * @return {Boolean} True if the values are equal
41517      */
41518     isEqual: function(a, b){
41519         if (Ext.isDate(a) && Ext.isDate(b)) {
41520             return a.getTime() === b.getTime();
41521         }
41522         return a === b;
41523     },
41524
41525     /**
41526      * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
41527      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
41528      */
41529     beginEdit : function(){
41530         var me = this;
41531         if (!me.editing) {
41532             me.editing = true;
41533             me.dirtySave = me.dirty;
41534             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
41535             me.modifiedSave = Ext.apply({}, me.modified);
41536         }
41537     },
41538
41539     /**
41540      * Cancels all changes made in the current edit operation.
41541      */
41542     cancelEdit : function(){
41543         var me = this;
41544         if (me.editing) {
41545             me.editing = false;
41546             // reset the modified state, nothing changed since the edit began
41547             me.modified = me.modifiedSave;
41548             me[me.persistenceProperty] = me.dataSave;
41549             me.dirty = me.dirtySave;
41550             delete me.modifiedSave;
41551             delete me.dataSave;
41552             delete me.dirtySave;
41553         }
41554     },
41555
41556     /**
41557      * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
41558      * fire).
41559      * @param {Boolean} silent True to not notify the store of the change
41560      */
41561     endEdit : function(silent){
41562         var me = this,
41563             didChange;
41564             
41565         if (me.editing) {
41566             me.editing = false;
41567             didChange = me.dirty || me.changedWhileEditing();
41568             delete me.modifiedSave;
41569             delete me.dataSave;
41570             delete me.dirtySave;
41571             if (silent !== true && didChange) {
41572                 me.afterEdit();
41573             }
41574         }
41575     },
41576     
41577     /**
41578      * Checks if the underlying data has changed during an edit. This doesn't necessarily
41579      * mean the record is dirty, however we still need to notify the store since it may need
41580      * to update any views.
41581      * @private
41582      * @return {Boolean} True if the underlying data has changed during an edit.
41583      */
41584     changedWhileEditing: function(){
41585         var me = this,
41586             saved = me.dataSave,
41587             data = me[me.persistenceProperty],
41588             key;
41589             
41590         for (key in data) {
41591             if (data.hasOwnProperty(key)) {
41592                 if (!me.isEqual(data[key], saved[key])) {
41593                     return true;
41594                 }
41595             }
41596         }
41597         return false; 
41598     },
41599
41600     /**
41601      * Gets a hash of only the fields that have been modified since this Model was created or commited.
41602      * @return {Object}
41603      */
41604     getChanges : function(){
41605         var modified = this.modified,
41606             changes  = {},
41607             field;
41608
41609         for (field in modified) {
41610             if (modified.hasOwnProperty(field)){
41611                 changes[field] = this.get(field);
41612             }
41613         }
41614
41615         return changes;
41616     },
41617
41618     /**
41619      * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
41620      * @param {String} fieldName {@link Ext.data.Field#name}
41621      * @return {Boolean}
41622      */
41623     isModified : function(fieldName) {
41624         return this.modified.hasOwnProperty(fieldName);
41625     },
41626
41627     /**
41628      * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
41629      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
41630      *
41631      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
41632      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
41633      */
41634     setDirty : function() {
41635         var me = this,
41636             name;
41637
41638         me.dirty = true;
41639
41640         me.fields.each(function(field) {
41641             if (field.persist) {
41642                 name = field.name;
41643                 me.modified[name] = me.get(name);
41644             }
41645         }, me);
41646     },
41647
41648     markDirty : function() {
41649         if (Ext.isDefined(Ext.global.console)) {
41650             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
41651         }
41652         return this.setDirty.apply(this, arguments);
41653     },
41654
41655     /**
41656      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
41657      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
41658      * reverted to their original values.
41659      *
41660      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
41661      * operations.
41662      *
41663      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41664      * Defaults to false.
41665      */
41666     reject : function(silent) {
41667         var me = this,
41668             modified = me.modified,
41669             field;
41670
41671         for (field in modified) {
41672             if (modified.hasOwnProperty(field)) {
41673                 if (typeof modified[field] != "function") {
41674                     me[me.persistenceProperty][field] = modified[field];
41675                 }
41676             }
41677         }
41678
41679         me.dirty = false;
41680         me.editing = false;
41681         me.modified = {};
41682
41683         if (silent !== true) {
41684             me.afterReject();
41685         }
41686     },
41687
41688     /**
41689      * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
41690      * instance since either creation or the last commit operation.
41691      *
41692      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
41693      * operations.
41694      *
41695      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41696      * Defaults to false.
41697      */
41698     commit : function(silent) {
41699         var me = this;
41700
41701         me.phantom = me.dirty = me.editing = false;
41702         me.modified = {};
41703
41704         if (silent !== true) {
41705             me.afterCommit();
41706         }
41707     },
41708
41709     /**
41710      * Creates a copy (clone) of this Model instance.
41711      *
41712      * @param {String} [id] A new id, defaults to the id of the instance being copied.
41713      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
41714      *
41715      *     var rec = record.copy(); // clone the record
41716      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
41717      *
41718      * @return {Ext.data.Model}
41719      */
41720     copy : function(newId) {
41721         var me = this;
41722
41723         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
41724     },
41725
41726     /**
41727      * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41728      * {@link Ext#createByAlias Ext.createByAlias}.
41729      *
41730      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41731      * @return {Ext.data.proxy.Proxy}
41732      */
41733     setProxy: function(proxy) {
41734         //make sure we have an Ext.data.proxy.Proxy object
41735         if (!proxy.isProxy) {
41736             if (typeof proxy === "string") {
41737                 proxy = {
41738                     type: proxy
41739                 };
41740             }
41741             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41742         }
41743         proxy.setModel(this.self);
41744         this.proxy = proxy;
41745
41746         return proxy;
41747     },
41748
41749     /**
41750      * Returns the configured Proxy for this Model.
41751      * @return {Ext.data.proxy.Proxy} The proxy
41752      */
41753     getProxy: function() {
41754         return this.proxy;
41755     },
41756
41757     /**
41758      * Validates the current data against all of its configured {@link #validations}.
41759      * @return {Ext.data.Errors} The errors object
41760      */
41761     validate: function() {
41762         var errors      = Ext.create('Ext.data.Errors'),
41763             validations = this.validations,
41764             validators  = Ext.data.validations,
41765             length, validation, field, valid, type, i;
41766
41767         if (validations) {
41768             length = validations.length;
41769
41770             for (i = 0; i < length; i++) {
41771                 validation = validations[i];
41772                 field = validation.field || validation.name;
41773                 type  = validation.type;
41774                 valid = validators[type](validation, this.get(field));
41775
41776                 if (!valid) {
41777                     errors.add({
41778                         field  : field,
41779                         message: validation.message || validators[type + 'Message']
41780                     });
41781                 }
41782             }
41783         }
41784
41785         return errors;
41786     },
41787
41788     /**
41789      * Checks if the model is valid. See {@link #validate}.
41790      * @return {Boolean} True if the model is valid.
41791      */
41792     isValid: function(){
41793         return this.validate().isValid();
41794     },
41795
41796     /**
41797      * Saves the model instance using the configured proxy.
41798      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41799      * @return {Ext.data.Model} The Model instance
41800      */
41801     save: function(options) {
41802         options = Ext.apply({}, options);
41803
41804         var me     = this,
41805             action = me.phantom ? 'create' : 'update',
41806             record = null,
41807             scope  = options.scope || me,
41808             operation,
41809             callback;
41810
41811         Ext.apply(options, {
41812             records: [me],
41813             action : action
41814         });
41815
41816         operation = Ext.create('Ext.data.Operation', options);
41817
41818         callback = function(operation) {
41819             if (operation.wasSuccessful()) {
41820                 record = operation.getRecords()[0];
41821                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
41822                 //ModelCache is in place
41823                 me.set(record.data);
41824                 record.dirty = false;
41825
41826                 Ext.callback(options.success, scope, [record, operation]);
41827             } else {
41828                 Ext.callback(options.failure, scope, [record, operation]);
41829             }
41830
41831             Ext.callback(options.callback, scope, [record, operation]);
41832         };
41833
41834         me.getProxy()[action](operation, callback, me);
41835
41836         return me;
41837     },
41838
41839     /**
41840      * Destroys the model using the configured proxy.
41841      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41842      * @return {Ext.data.Model} The Model instance
41843      */
41844     destroy: function(options){
41845         options = Ext.apply({}, options);
41846
41847         var me     = this,
41848             record = null,
41849             scope  = options.scope || me,
41850             operation,
41851             callback;
41852
41853         Ext.apply(options, {
41854             records: [me],
41855             action : 'destroy'
41856         });
41857
41858         operation = Ext.create('Ext.data.Operation', options);
41859         callback = function(operation) {
41860             if (operation.wasSuccessful()) {
41861                 Ext.callback(options.success, scope, [record, operation]);
41862             } else {
41863                 Ext.callback(options.failure, scope, [record, operation]);
41864             }
41865             Ext.callback(options.callback, scope, [record, operation]);
41866         };
41867
41868         me.getProxy().destroy(operation, callback, me);
41869         return me;
41870     },
41871
41872     /**
41873      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
41874      * @return {Number} The id
41875      */
41876     getId: function() {
41877         return this.get(this.idProperty);
41878     },
41879
41880     /**
41881      * Sets the model instance's id field to the given id.
41882      * @param {Number} id The new id
41883      */
41884     setId: function(id) {
41885         this.set(this.idProperty, id);
41886     },
41887
41888     /**
41889      * Tells this model instance that it has been added to a store.
41890      * @param {Ext.data.Store} store The store to which this model has been added.
41891      */
41892     join : function(store) {
41893         /**
41894          * @property {Ext.data.Store} store
41895          * The {@link Ext.data.Store Store} to which this Record belongs.
41896          */
41897         this.store = store;
41898     },
41899
41900     /**
41901      * Tells this model instance that it has been removed from the store.
41902      * @param {Ext.data.Store} store The store from which this model has been removed.
41903      */
41904     unjoin: function(store) {
41905         delete this.store;
41906     },
41907
41908     /**
41909      * @private
41910      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41911      * afterEdit method is called
41912      */
41913     afterEdit : function() {
41914         this.callStore('afterEdit');
41915     },
41916
41917     /**
41918      * @private
41919      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41920      * afterReject method is called
41921      */
41922     afterReject : function() {
41923         this.callStore("afterReject");
41924     },
41925
41926     /**
41927      * @private
41928      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41929      * afterCommit method is called
41930      */
41931     afterCommit: function() {
41932         this.callStore('afterCommit');
41933     },
41934
41935     /**
41936      * @private
41937      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
41938      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
41939      * will always be called with the model instance as its single argument.
41940      * @param {String} fn The function to call on the store
41941      */
41942     callStore: function(fn) {
41943         var store = this.store;
41944
41945         if (store !== undefined && typeof store[fn] == "function") {
41946             store[fn](this);
41947         }
41948     },
41949
41950     /**
41951      * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
41952      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
41953      *
41954      *     {
41955      *         orders: [
41956      *             {
41957      *                 id: 123,
41958      *                 status: 'shipped',
41959      *                 orderItems: [
41960      *                     ...
41961      *                 ]
41962      *             }
41963      *         ]
41964      *     }
41965      *
41966      * @return {Object} The nested data set for the Model's loaded associations
41967      */
41968     getAssociatedData: function(){
41969         return this.prepareAssociatedData(this, [], null);
41970     },
41971
41972     /**
41973      * @private
41974      * This complex-looking method takes a given Model instance and returns an object containing all data from
41975      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
41976      * @param {Ext.data.Model} record The Model instance
41977      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
41978      * @param {String} associationType (optional) The name of the type of association to limit to.
41979      * @return {Object} The nested data set for the Model's loaded associations
41980      */
41981     prepareAssociatedData: function(record, ids, associationType) {
41982         //we keep track of all of the internalIds of the models that we have loaded so far in here
41983         var associations     = record.associations.items,
41984             associationCount = associations.length,
41985             associationData  = {},
41986             associatedStore, associatedName, associatedRecords, associatedRecord,
41987             associatedRecordCount, association, id, i, j, type, allow;
41988
41989         for (i = 0; i < associationCount; i++) {
41990             association = associations[i];
41991             type = association.type;
41992             allow = true;
41993             if (associationType) {
41994                 allow = type == associationType;
41995             }
41996             if (allow && type == 'hasMany') {
41997
41998                 //this is the hasMany store filled with the associated data
41999                 associatedStore = record[association.storeName];
42000
42001                 //we will use this to contain each associated record's data
42002                 associationData[association.name] = [];
42003
42004                 //if it's loaded, put it into the association data
42005                 if (associatedStore && associatedStore.data.length > 0) {
42006                     associatedRecords = associatedStore.data.items;
42007                     associatedRecordCount = associatedRecords.length;
42008
42009                     //now we're finally iterating over the records in the association. We do this recursively
42010                     for (j = 0; j < associatedRecordCount; j++) {
42011                         associatedRecord = associatedRecords[j];
42012                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
42013                         id = associatedRecord.id;
42014
42015                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
42016                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
42017                         if (Ext.Array.indexOf(ids, id) == -1) {
42018                             ids.push(id);
42019
42020                             associationData[association.name][j] = associatedRecord.data;
42021                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
42022                         }
42023                     }
42024                 }
42025             } else if (allow && type == 'belongsTo') {
42026                 associatedRecord = record[association.instanceName];
42027                 if (associatedRecord !== undefined) {
42028                     id = associatedRecord.id;
42029                     if (Ext.Array.indexOf(ids, id) == -1) {
42030                         ids.push(id);
42031                         associationData[association.name] = associatedRecord.data;
42032                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
42033                     }
42034                 }
42035             }
42036         }
42037
42038         return associationData;
42039     }
42040 });
42041
42042 /**
42043  * @docauthor Evan Trimboli <evan@sencha.com>
42044  *
42045  * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
42046  * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
42047  * referred to via it's identifier:
42048  *
42049  *     Ext.create('Ext.data.Store', {
42050  *         model: 'SomeModel',
42051  *         storeId: 'myStore'
42052  *     });
42053  *
42054  *     var store = Ext.data.StoreManager.lookup('myStore');
42055  *
42056  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
42057  *
42058  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
42059  * it with any Component that consumes data from a store:
42060  *
42061  *     Ext.create('Ext.data.Store', {
42062  *         model: 'SomeModel',
42063  *         storeId: 'myStore'
42064  *     });
42065  *
42066  *     Ext.create('Ext.view.View', {
42067  *         store: 'myStore',
42068  *         // other configuration here
42069  *     });
42070  *
42071  */
42072 Ext.define('Ext.data.StoreManager', {
42073     extend: 'Ext.util.MixedCollection',
42074     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
42075     singleton: true,
42076     uses: ['Ext.data.ArrayStore'],
42077     
42078     /**
42079      * @cfg {Object} listeners @hide
42080      */
42081
42082     /**
42083      * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
42084      * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
42085      * @param {Ext.data.Store...} stores Any number of Store instances
42086      */
42087     register : function() {
42088         for (var i = 0, s; (s = arguments[i]); i++) {
42089             this.add(s);
42090         }
42091     },
42092
42093     /**
42094      * Unregisters one or more Stores with the StoreManager
42095      * @param {String/Object...} stores Any number of Store instances or ID-s
42096      */
42097     unregister : function() {
42098         for (var i = 0, s; (s = arguments[i]); i++) {
42099             this.remove(this.lookup(s));
42100         }
42101     },
42102
42103     /**
42104      * Gets a registered Store by id
42105      * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
42106      * @return {Ext.data.Store}
42107      */
42108     lookup : function(store) {
42109         // handle the case when we are given an array or an array of arrays.
42110         if (Ext.isArray(store)) {
42111             var fields = ['field1'], 
42112                 expand = !Ext.isArray(store[0]),
42113                 data = store,
42114                 i,
42115                 len;
42116                 
42117             if(expand){
42118                 data = [];
42119                 for (i = 0, len = store.length; i < len; ++i) {
42120                     data.push([store[i]]);
42121                 }
42122             } else {
42123                 for(i = 2, len = store[0].length; i <= len; ++i){
42124                     fields.push('field' + i);
42125                 }
42126             }
42127             return Ext.create('Ext.data.ArrayStore', {
42128                 data  : data,
42129                 fields: fields,
42130                 autoDestroy: true,
42131                 autoCreated: true,
42132                 expanded: expand
42133             });
42134         }
42135         
42136         if (Ext.isString(store)) {
42137             // store id
42138             return this.get(store);
42139         } else {
42140             // store instance or store config
42141             return Ext.data.AbstractStore.create(store);
42142         }
42143     },
42144
42145     // getKey implementation for MixedCollection
42146     getKey : function(o) {
42147          return o.storeId;
42148     }
42149 }, function() {    
42150     /**
42151      * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
42152      * Sample usage:
42153      *
42154      *     Ext.regStore('AllUsers', {
42155      *         model: 'User'
42156      *     });
42157      *
42158      *     // the store can now easily be used throughout the application
42159      *     new Ext.List({
42160      *         store: 'AllUsers',
42161      *         ... other config
42162      *     });
42163      *
42164      * @param {String} id The id to set on the new store
42165      * @param {Object} config The store config
42166      * @member Ext
42167      * @method regStore
42168      */
42169     Ext.regStore = function(name, config) {
42170         var store;
42171
42172         if (Ext.isObject(name)) {
42173             config = name;
42174         } else {
42175             config.storeId = name;
42176         }
42177
42178         if (config instanceof Ext.data.Store) {
42179             store = config;
42180         } else {
42181             store = Ext.create('Ext.data.Store', config);
42182         }
42183
42184         return Ext.data.StoreManager.register(store);
42185     };
42186
42187     /**
42188      * Shortcut to {@link Ext.data.StoreManager#lookup}.
42189      * @member Ext
42190      * @method getStore
42191      * @alias Ext.data.StoreManager#lookup
42192      */
42193     Ext.getStore = function(name) {
42194         return Ext.data.StoreManager.lookup(name);
42195     };
42196 });
42197
42198 /**
42199  * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
42200  * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
42201  * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
42202  * at the time the Container is created, or they may be added dynamically via the
42203  * {@link Ext.container.Container#add add} method.
42204  *
42205  * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
42206  *
42207  * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
42208  * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
42209  *
42210  * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
42211  * subclass Component.
42212  *
42213  * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
42214  * to create custom Components.
42215  *
42216  * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
42217  * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
42218  * Component hierarchy.
42219  *
42220  * This is the list of all valid xtypes:
42221  *
42222  *     xtype            Class
42223  *     -------------    ------------------
42224  *     button           {@link Ext.button.Button}
42225  *     buttongroup      {@link Ext.container.ButtonGroup}
42226  *     colorpalette     {@link Ext.picker.Color}
42227  *     component        {@link Ext.Component}
42228  *     container        {@link Ext.container.Container}
42229  *     cycle            {@link Ext.button.Cycle}
42230  *     dataview         {@link Ext.view.View}
42231  *     datepicker       {@link Ext.picker.Date}
42232  *     editor           {@link Ext.Editor}
42233  *     editorgrid       {@link Ext.grid.plugin.Editing}
42234  *     grid             {@link Ext.grid.Panel}
42235  *     multislider      {@link Ext.slider.Multi}
42236  *     panel            {@link Ext.panel.Panel}
42237  *     progressbar      {@link Ext.ProgressBar}
42238  *     slider           {@link Ext.slider.Single}
42239  *     splitbutton      {@link Ext.button.Split}
42240  *     tabpanel         {@link Ext.tab.Panel}
42241  *     treepanel        {@link Ext.tree.Panel}
42242  *     viewport         {@link Ext.container.Viewport}
42243  *     window           {@link Ext.window.Window}
42244  *
42245  *     Toolbar components
42246  *     ---------------------------------------
42247  *     pagingtoolbar    {@link Ext.toolbar.Paging}
42248  *     toolbar          {@link Ext.toolbar.Toolbar}
42249  *     tbfill           {@link Ext.toolbar.Fill}
42250  *     tbitem           {@link Ext.toolbar.Item}
42251  *     tbseparator      {@link Ext.toolbar.Separator}
42252  *     tbspacer         {@link Ext.toolbar.Spacer}
42253  *     tbtext           {@link Ext.toolbar.TextItem}
42254  *
42255  *     Menu components
42256  *     ---------------------------------------
42257  *     menu             {@link Ext.menu.Menu}
42258  *     menucheckitem    {@link Ext.menu.CheckItem}
42259  *     menuitem         {@link Ext.menu.Item}
42260  *     menuseparator    {@link Ext.menu.Separator}
42261  *     menutextitem     {@link Ext.menu.Item}
42262  *
42263  *     Form components
42264  *     ---------------------------------------
42265  *     form             {@link Ext.form.Panel}
42266  *     checkbox         {@link Ext.form.field.Checkbox}
42267  *     combo            {@link Ext.form.field.ComboBox}
42268  *     datefield        {@link Ext.form.field.Date}
42269  *     displayfield     {@link Ext.form.field.Display}
42270  *     field            {@link Ext.form.field.Base}
42271  *     fieldset         {@link Ext.form.FieldSet}
42272  *     hidden           {@link Ext.form.field.Hidden}
42273  *     htmleditor       {@link Ext.form.field.HtmlEditor}
42274  *     label            {@link Ext.form.Label}
42275  *     numberfield      {@link Ext.form.field.Number}
42276  *     radio            {@link Ext.form.field.Radio}
42277  *     radiogroup       {@link Ext.form.RadioGroup}
42278  *     textarea         {@link Ext.form.field.TextArea}
42279  *     textfield        {@link Ext.form.field.Text}
42280  *     timefield        {@link Ext.form.field.Time}
42281  *     trigger          {@link Ext.form.field.Trigger}
42282  *
42283  *     Chart components
42284  *     ---------------------------------------
42285  *     chart            {@link Ext.chart.Chart}
42286  *     barchart         {@link Ext.chart.series.Bar}
42287  *     columnchart      {@link Ext.chart.series.Column}
42288  *     linechart        {@link Ext.chart.series.Line}
42289  *     piechart         {@link Ext.chart.series.Pie}
42290  *
42291  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
42292  * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
42293  * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
42294  *
42295  *     @example
42296  *     Ext.create('Ext.Component', {
42297  *         html: 'Hello world!',
42298  *         width: 300,
42299  *         height: 200,
42300  *         padding: 20,
42301  *         style: {
42302  *             color: '#FFFFFF',
42303  *             backgroundColor:'#000000'
42304  *         },
42305  *         renderTo: Ext.getBody()
42306  *     });
42307  *
42308  * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
42309  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
42310  * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
42311  * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
42312  *
42313  * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
42314  */
42315 Ext.define('Ext.Component', {
42316
42317     /* Begin Definitions */
42318
42319     alias: ['widget.component', 'widget.box'],
42320
42321     extend: 'Ext.AbstractComponent',
42322
42323     requires: [
42324         'Ext.util.DelayedTask'
42325     ],
42326
42327     uses: [
42328         'Ext.Layer',
42329         'Ext.resizer.Resizer',
42330         'Ext.util.ComponentDragger'
42331     ],
42332
42333     mixins: {
42334         floating: 'Ext.util.Floating'
42335     },
42336
42337     statics: {
42338         // Collapse/expand directions
42339         DIRECTION_TOP: 'top',
42340         DIRECTION_RIGHT: 'right',
42341         DIRECTION_BOTTOM: 'bottom',
42342         DIRECTION_LEFT: 'left',
42343
42344         VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
42345
42346         // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
42347         // This includes dot, comma and whitespace
42348         INVALID_ID_CHARS_Re: /[\.,\s]/g
42349     },
42350
42351     /* End Definitions */
42352
42353     /**
42354      * @cfg {Boolean/Object} resizable
42355      * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
42356      *
42357      * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
42358      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
42359      * `{@link Ext.resizer.Resizer#dynamic}: false`
42360      */
42361
42362     /**
42363      * @cfg {String} resizeHandles
42364      * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
42365      */
42366     resizeHandles: 'all',
42367
42368     /**
42369      * @cfg {Boolean} [autoScroll=false]
42370      * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
42371      * `false` to clip any overflowing content.
42372      */
42373
42374     /**
42375      * @cfg {Boolean} floating
42376      * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
42377      *
42378      * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
42379      *
42380      * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
42381      * the global {@link Ext.WindowManager ZIndexManager}
42382      *
42383      * ### Floating Components as child items of a Container
42384      *
42385      * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
42386      * a ZIndexManager by examining the ownerCt chain.
42387      *
42388      * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
42389      * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
42390      * of its stack when the Component's {@link #toFront} method is called.
42391      *
42392      * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
42393      * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
42394      * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
42395      * ancestor Container.
42396      *
42397      * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
42398      * ZIndexManager}.
42399      *
42400      * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
42401      * you explicitly {@link #show} them.
42402      *
42403      * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
42404      * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
42405      * not be set.
42406      */
42407     floating: false,
42408
42409     /**
42410      * @cfg {Boolean} toFrontOnShow
42411      * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
42412      * floating component.
42413      */
42414     toFrontOnShow: true,
42415
42416     /**
42417      * @property {Ext.ZIndexManager} zIndexManager
42418      * Only present for {@link #floating} Components after they have been rendered.
42419      *
42420      * A reference to the ZIndexManager which is managing this Component's z-index.
42421      *
42422      * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
42423      * a single modal mask which is insert just beneath the topmost visible modal floating Component.
42424      *
42425      * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
42426      * z-index stack.
42427      *
42428      * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
42429      * programatically {@link Ext.Component#render rendered}.
42430      *
42431      * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
42432      * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
42433      * used.
42434      *
42435      * See {@link #floating} and {@link #floatParent}
42436      */
42437
42438     /**
42439      * @property {Ext.Container} floatParent
42440      * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
42441      *
42442      * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
42443      * property.
42444      *
42445      * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
42446      * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
42447      * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
42448      * Components.
42449      *
42450      * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
42451      * Window as its `floatParent`
42452      *
42453      * See {@link #floating} and {@link #zIndexManager}
42454      */
42455
42456     /**
42457      * @cfg {Boolean/Object} [draggable=false]
42458      * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
42459      * the drag handle.
42460      *
42461      * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
42462      * instantiated to perform dragging.
42463      *
42464      * For example to create a Component which may only be dragged around using a certain internal element as the drag
42465      * handle, use the delegate option:
42466      *
42467      *     new Ext.Component({
42468      *         constrain: true,
42469      *         floating: true,
42470      *         style: {
42471      *             backgroundColor: '#fff',
42472      *             border: '1px solid black'
42473      *         },
42474      *         html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
42475      *         draggable: {
42476      *             delegate: 'h1'
42477      *         }
42478      *     }).show();
42479      */
42480
42481     /**
42482      * @cfg {Boolean} [maintainFlex=false]
42483      * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
42484      * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
42485      *
42486      * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
42487      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
42488      */
42489
42490     hideMode: 'display',
42491     // Deprecate 5.0
42492     hideParent: false,
42493
42494     ariaRole: 'presentation',
42495
42496     bubbleEvents: [],
42497
42498     actionMode: 'el',
42499     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
42500
42501     //renderTpl: new Ext.XTemplate(
42502     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
42503     //        compiled: true,
42504     //        disableFormats: true
42505     //    }
42506     //),
42507
42508     /**
42509      * Creates new Component.
42510      * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
42511      *
42512      * - **an element** : it is set as the internal element and its id used as the component id
42513      * - **a string** : it is assumed to be the id of an existing element and is used as the component id
42514      * - **anything else** : it is assumed to be a standard config object and is applied to the component
42515      */
42516     constructor: function(config) {
42517         var me = this;
42518
42519         config = config || {};
42520         if (config.initialConfig) {
42521
42522             // Being initialized from an Ext.Action instance...
42523             if (config.isAction) {
42524                 me.baseAction = config;
42525             }
42526             config = config.initialConfig;
42527             // component cloning / action set up
42528         }
42529         else if (config.tagName || config.dom || Ext.isString(config)) {
42530             // element object
42531             config = {
42532                 applyTo: config,
42533                 id: config.id || config
42534             };
42535         }
42536
42537         me.callParent([config]);
42538
42539         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
42540         // register this Component as one of its items
42541         if (me.baseAction){
42542             me.baseAction.addComponent(me);
42543         }
42544     },
42545
42546     /**
42547      * The initComponent template method is an important initialization step for a Component. It is intended to be
42548      * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
42549      * initComponent method of the class being created is called first, with each initComponent method
42550      * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
42551      * if needed, override the constructor logic of the Component at any step in the hierarchy.
42552      *
42553      * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
42554      * to ensure that the parent class' initComponent method is also called.
42555      *
42556      * The following example demonstrates using a dynamic string for the text of a button at the time of
42557      * instantiation of the class.
42558      *
42559      *     Ext.define('DynamicButtonText', {
42560      *         extend: 'Ext.button.Button',
42561      *
42562      *         initComponent: function() {
42563      *             this.text = new Date();
42564      *             this.renderTo = Ext.getBody();
42565      *             this.callParent();
42566      *         }
42567      *     });
42568      *
42569      *     Ext.onReady(function() {
42570      *         Ext.create('DynamicButtonText');
42571      *     });
42572      *
42573      * @template
42574      */
42575     initComponent: function() {
42576         var me = this;
42577
42578         me.callParent();
42579
42580         if (me.listeners) {
42581             me.on(me.listeners);
42582             delete me.listeners;
42583         }
42584         me.enableBubble(me.bubbleEvents);
42585         me.mons = [];
42586     },
42587
42588     // private
42589     afterRender: function() {
42590         var me = this,
42591             resizable = me.resizable;
42592
42593         if (me.floating) {
42594             me.makeFloating(me.floating);
42595         } else {
42596             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
42597         }
42598
42599         if (Ext.isDefined(me.autoScroll)) {
42600             me.setAutoScroll(me.autoScroll);
42601         }
42602         me.callParent();
42603
42604         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
42605             me.setPagePosition(me.pageX, me.pageY);
42606         }
42607
42608         if (resizable) {
42609             me.initResizable(resizable);
42610         }
42611
42612         if (me.draggable) {
42613             me.initDraggable();
42614         }
42615
42616         me.initAria();
42617     },
42618
42619     initAria: function() {
42620         var actionEl = this.getActionEl(),
42621             role = this.ariaRole;
42622         if (role) {
42623             actionEl.dom.setAttribute('role', role);
42624         }
42625     },
42626
42627     /**
42628      * Sets the overflow on the content element of the component.
42629      * @param {Boolean} scroll True to allow the Component to auto scroll.
42630      * @return {Ext.Component} this
42631      */
42632     setAutoScroll : function(scroll){
42633         var me = this,
42634             targetEl;
42635         scroll = !!scroll;
42636         if (me.rendered) {
42637             targetEl = me.getTargetEl();
42638             targetEl.setStyle('overflow', scroll ? 'auto' : '');
42639             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
42640                 // The scrollable container element must be non-statically positioned or IE6/7 will make
42641                 // positioned children stay in place rather than scrolling with the rest of the content
42642                 targetEl.position();
42643             }
42644         }
42645         me.autoScroll = scroll;
42646         return me;
42647     },
42648
42649     // private
42650     makeFloating : function(cfg){
42651         this.mixins.floating.constructor.call(this, cfg);
42652     },
42653
42654     initResizable: function(resizable) {
42655         var me = this;
42656
42657         resizable = Ext.apply({
42658             target: me,
42659             dynamic: false,
42660             constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
42661             handles: me.resizeHandles
42662         }, resizable);
42663         resizable.target = me;
42664         me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
42665     },
42666
42667     getDragEl: function() {
42668         return this.el;
42669     },
42670
42671     initDraggable: function() {
42672         var me = this,
42673             ddConfig = Ext.applyIf({
42674                 el: me.getDragEl(),
42675                 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
42676             }, me.draggable);
42677
42678         // Add extra configs if Component is specified to be constrained
42679         if (me.constrain || me.constrainDelegate) {
42680             ddConfig.constrain = me.constrain;
42681             ddConfig.constrainDelegate = me.constrainDelegate;
42682         }
42683
42684         me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
42685     },
42686
42687     /**
42688      * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
42689      * method fires the {@link #move} event.
42690      * @param {Number} left The new left
42691      * @param {Number} top The new top
42692      * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
42693      * animation configuration.
42694      * @return {Ext.Component} this
42695      */
42696     setPosition: function(x, y, animate) {
42697         var me = this,
42698             el = me.el,
42699             to = {},
42700             adj, adjX, adjY, xIsNumber, yIsNumber;
42701
42702         if (Ext.isArray(x)) {
42703             animate = y;
42704             y = x[1];
42705             x = x[0];
42706         }
42707         me.x = x;
42708         me.y = y;
42709
42710         if (!me.rendered) {
42711             return me;
42712         }
42713
42714         adj = me.adjustPosition(x, y);
42715         adjX = adj.x;
42716         adjY = adj.y;
42717         xIsNumber = Ext.isNumber(adjX);
42718         yIsNumber = Ext.isNumber(adjY);
42719
42720         if (xIsNumber || yIsNumber) {
42721             if (animate) {
42722                 if (xIsNumber) {
42723                     to.left = adjX;
42724                 }
42725                 if (yIsNumber) {
42726                     to.top = adjY;
42727                 }
42728
42729                 me.stopAnimation();
42730                 me.animate(Ext.apply({
42731                     duration: 1000,
42732                     listeners: {
42733                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
42734                     },
42735                     to: to
42736                 }, animate));
42737             }
42738             else {
42739                 if (!xIsNumber) {
42740                     el.setTop(adjY);
42741                 }
42742                 else if (!yIsNumber) {
42743                     el.setLeft(adjX);
42744                 }
42745                 else {
42746                     el.setLeftTop(adjX, adjY);
42747                 }
42748                 me.afterSetPosition(adjX, adjY);
42749             }
42750         }
42751         return me;
42752     },
42753
42754     /**
42755      * @private
42756      * @template
42757      * Template method called after a Component has been positioned.
42758      */
42759     afterSetPosition: function(ax, ay) {
42760         this.onPosition(ax, ay);
42761         this.fireEvent('move', this, ax, ay);
42762     },
42763
42764     /**
42765      * Displays component at specific xy position.
42766      * A floating component (like a menu) is positioned relative to its ownerCt if any.
42767      * Useful for popping up a context menu:
42768      *
42769      *     listeners: {
42770      *         itemcontextmenu: function(view, record, item, index, event, options) {
42771      *             Ext.create('Ext.menu.Menu', {
42772      *                 width: 100,
42773      *                 height: 100,
42774      *                 margin: '0 0 10 0',
42775      *                 items: [{
42776      *                     text: 'regular item 1'
42777      *                 },{
42778      *                     text: 'regular item 2'
42779      *                 },{
42780      *                     text: 'regular item 3'
42781      *                 }]
42782      *             }).showAt(event.getXY());
42783      *         }
42784      *     }
42785      *
42786      * @param {Number} x The new x position
42787      * @param {Number} y The new y position
42788      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42789      * animation configuration.
42790      */
42791     showAt: function(x, y, animate) {
42792         var me = this;
42793
42794         if (me.floating) {
42795             me.setPosition(x, y, animate);
42796         } else {
42797             me.setPagePosition(x, y, animate);
42798         }
42799         me.show();
42800     },
42801
42802     /**
42803      * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
42804      * This method fires the {@link #move} event.
42805      * @param {Number} x The new x position
42806      * @param {Number} y The new y position
42807      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42808      * animation configuration.
42809      * @return {Ext.Component} this
42810      */
42811     setPagePosition: function(x, y, animate) {
42812         var me = this,
42813             p;
42814
42815         if (Ext.isArray(x)) {
42816             y = x[1];
42817             x = x[0];
42818         }
42819         me.pageX = x;
42820         me.pageY = y;
42821         if (me.floating && me.floatParent) {
42822             // Floating Components being positioned in their ownerCt have to be made absolute
42823             p = me.floatParent.getTargetEl().getViewRegion();
42824             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
42825                 x -= p.left;
42826             }
42827             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
42828                 y -= p.top;
42829             }
42830             me.setPosition(x, y, animate);
42831         }
42832         else {
42833             p = me.el.translatePoints(x, y);
42834             me.setPosition(p.left, p.top, animate);
42835         }
42836         return me;
42837     },
42838
42839     /**
42840      * Gets the current box measurements of the component's underlying element.
42841      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42842      * @return {Object} box An object in the format {x, y, width, height}
42843      */
42844     getBox : function(local){
42845         var pos = this.getPosition(local),
42846             size = this.getSize();
42847
42848         size.x = pos[0];
42849         size.y = pos[1];
42850         return size;
42851     },
42852
42853     /**
42854      * Sets the current box measurements of the component's underlying element.
42855      * @param {Object} box An object in the format {x, y, width, height}
42856      * @return {Ext.Component} this
42857      */
42858     updateBox : function(box){
42859         this.setSize(box.width, box.height);
42860         this.setPagePosition(box.x, box.y);
42861         return this;
42862     },
42863
42864     // Include margins
42865     getOuterSize: function() {
42866         var el = this.el;
42867         return {
42868             width: el.getWidth() + el.getMargin('lr'),
42869             height: el.getHeight() + el.getMargin('tb')
42870         };
42871     },
42872
42873     // private
42874     adjustPosition: function(x, y) {
42875
42876         // Floating Components being positioned in their ownerCt have to be made absolute
42877         if (this.floating && this.floatParent) {
42878             var o = this.floatParent.getTargetEl().getViewRegion();
42879             x += o.left;
42880             y += o.top;
42881         }
42882
42883         return {
42884             x: x,
42885             y: y
42886         };
42887     },
42888
42889     /**
42890      * Gets the current XY position of the component's underlying element.
42891      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42892      * @return {Number[]} The XY position of the element (e.g., [100, 200])
42893      */
42894     getPosition: function(local) {
42895         var me = this,
42896             el = me.el,
42897             xy,
42898             o;
42899
42900         // Floating Components which were just rendered with no ownerCt return local position.
42901         if ((local === true) || (me.floating && !me.floatParent)) {
42902             return [el.getLeft(true), el.getTop(true)];
42903         }
42904         xy = me.xy || el.getXY();
42905
42906         // Floating Components in an ownerCt have to have their positions made relative
42907         if (me.floating) {
42908             o = me.floatParent.getTargetEl().getViewRegion();
42909             xy[0] -= o.left;
42910             xy[1] -= o.top;
42911         }
42912         return xy;
42913     },
42914
42915     getId: function() {
42916         var me = this,
42917             xtype;
42918
42919         if (!me.id) {
42920             xtype = me.getXType();
42921             xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
42922             me.id = xtype + '-' + me.getAutoId();
42923         }
42924         return me.id;
42925     },
42926
42927     onEnable: function() {
42928         var actionEl = this.getActionEl();
42929         actionEl.dom.removeAttribute('aria-disabled');
42930         actionEl.dom.disabled = false;
42931         this.callParent();
42932     },
42933
42934     onDisable: function() {
42935         var actionEl = this.getActionEl();
42936         actionEl.dom.setAttribute('aria-disabled', true);
42937         actionEl.dom.disabled = true;
42938         this.callParent();
42939     },
42940
42941     /**
42942      * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
42943      *
42944      * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
42945      * brought to the front of its {@link #zIndexManager z-index stack}.
42946      *
42947      * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
42948      * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
42949      * with `floating: true`.** The target from which the Component should animate from while opening.
42950      * @param {Function} [callback] A callback function to call after the Component is displayed.
42951      * Only necessary if animation was specified.
42952      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42953      * Defaults to this Component.
42954      * @return {Ext.Component} this
42955      */
42956     show: function(animateTarget, cb, scope) {
42957         var me = this;
42958
42959         if (me.rendered && me.isVisible()) {
42960             if (me.toFrontOnShow && me.floating) {
42961                 me.toFront();
42962             }
42963         } else if (me.fireEvent('beforeshow', me) !== false) {
42964             me.hidden = false;
42965
42966             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
42967             if (!me.rendered && (me.autoRender || me.floating)) {
42968                 me.doAutoRender();
42969             }
42970             if (me.rendered) {
42971                 me.beforeShow();
42972                 me.onShow.apply(me, arguments);
42973
42974                 // Notify any owning Container unless it's suspended.
42975                 // Floating Components do not participate in layouts.
42976                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42977                     me.ownerCt.doLayout();
42978                 }
42979                 me.afterShow.apply(me, arguments);
42980             }
42981         }
42982         return me;
42983     },
42984
42985     beforeShow: Ext.emptyFn,
42986
42987     // Private. Override in subclasses where more complex behaviour is needed.
42988     onShow: function() {
42989         var me = this;
42990
42991         me.el.show();
42992         me.callParent(arguments);
42993         if (me.floating && me.constrain) {
42994             me.doConstrain();
42995         }
42996     },
42997
42998     afterShow: function(animateTarget, cb, scope) {
42999         var me = this,
43000             fromBox,
43001             toBox,
43002             ghostPanel;
43003
43004         // Default to configured animate target if none passed
43005         animateTarget = animateTarget || me.animateTarget;
43006
43007         // Need to be able to ghost the Component
43008         if (!me.ghost) {
43009             animateTarget = null;
43010         }
43011         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
43012         if (animateTarget) {
43013             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
43014             toBox = me.el.getBox();
43015             fromBox = animateTarget.getBox();
43016             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
43017             ghostPanel = me.ghost();
43018             ghostPanel.el.stopAnimation();
43019
43020             // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
43021             ghostPanel.el.setX(-10000);
43022
43023             ghostPanel.el.animate({
43024                 from: fromBox,
43025                 to: toBox,
43026                 listeners: {
43027                     afteranimate: function() {
43028                         delete ghostPanel.componentLayout.lastComponentSize;
43029                         me.unghost();
43030                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
43031                         me.onShowComplete(cb, scope);
43032                     }
43033                 }
43034             });
43035         }
43036         else {
43037             me.onShowComplete(cb, scope);
43038         }
43039     },
43040
43041     onShowComplete: function(cb, scope) {
43042         var me = this;
43043         if (me.floating) {
43044             me.toFront();
43045         }
43046         Ext.callback(cb, scope || me);
43047         me.fireEvent('show', me);
43048     },
43049
43050     /**
43051      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
43052      * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
43053      * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
43054      * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
43055      * @param {Function} [callback] A callback function to call after the Component is hidden.
43056      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
43057      * Defaults to this Component.
43058      * @return {Ext.Component} this
43059      */
43060     hide: function() {
43061         var me = this;
43062
43063         // Clear the flag which is set if a floatParent was hidden while this is visible.
43064         // If a hide operation was subsequently called, that pending show must be hidden.
43065         me.showOnParentShow = false;
43066
43067         if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
43068             me.hidden = true;
43069             if (me.rendered) {
43070                 me.onHide.apply(me, arguments);
43071
43072                 // Notify any owning Container unless it's suspended.
43073                 // Floating Components do not participate in layouts.
43074                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
43075                     me.ownerCt.doLayout();
43076                 }
43077             }
43078         }
43079         return me;
43080     },
43081
43082     // Possibly animate down to a target element.
43083     onHide: function(animateTarget, cb, scope) {
43084         var me = this,
43085             ghostPanel,
43086             toBox;
43087
43088         // Default to configured animate target if none passed
43089         animateTarget = animateTarget || me.animateTarget;
43090
43091         // Need to be able to ghost the Component
43092         if (!me.ghost) {
43093             animateTarget = null;
43094         }
43095         // If we're animating, kick off an animation of the ghost down to the target
43096         if (animateTarget) {
43097             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
43098             ghostPanel = me.ghost();
43099             ghostPanel.el.stopAnimation();
43100             toBox = animateTarget.getBox();
43101             toBox.width += 'px';
43102             toBox.height += 'px';
43103             ghostPanel.el.animate({
43104                 to: toBox,
43105                 listeners: {
43106                     afteranimate: function() {
43107                         delete ghostPanel.componentLayout.lastComponentSize;
43108                         ghostPanel.el.hide();
43109                         me.afterHide(cb, scope);
43110                     }
43111                 }
43112             });
43113         }
43114         me.el.hide();
43115         if (!animateTarget) {
43116             me.afterHide(cb, scope);
43117         }
43118     },
43119
43120     afterHide: function(cb, scope) {
43121         Ext.callback(cb, scope || this);
43122         this.fireEvent('hide', this);
43123     },
43124
43125     /**
43126      * @private
43127      * @template
43128      * Template method to contribute functionality at destroy time.
43129      */
43130     onDestroy: function() {
43131         var me = this;
43132
43133         // Ensure that any ancillary components are destroyed.
43134         if (me.rendered) {
43135             Ext.destroy(
43136                 me.proxy,
43137                 me.proxyWrap,
43138                 me.resizer
43139             );
43140             // Different from AbstractComponent
43141             if (me.actionMode == 'container' || me.removeMode == 'container') {
43142                 me.container.remove();
43143             }
43144         }
43145         delete me.focusTask;
43146         me.callParent();
43147     },
43148
43149     deleteMembers: function() {
43150         var args = arguments,
43151             len = args.length,
43152             i = 0;
43153         for (; i < len; ++i) {
43154             delete this[args[i]];
43155         }
43156     },
43157
43158     /**
43159      * Try to focus this component.
43160      * @param {Boolean} [selectText] If applicable, true to also select the text in this component
43161      * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
43162      * @return {Ext.Component} this
43163      */
43164     focus: function(selectText, delay) {
43165         var me = this,
43166                 focusEl;
43167
43168         if (delay) {
43169             if (!me.focusTask) {
43170                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
43171             }
43172             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
43173             return me;
43174         }
43175
43176         if (me.rendered && !me.isDestroyed) {
43177             // getFocusEl could return a Component.
43178             focusEl = me.getFocusEl();
43179             focusEl.focus();
43180             if (focusEl.dom && selectText === true) {
43181                 focusEl.dom.select();
43182             }
43183
43184             // Focusing a floating Component brings it to the front of its stack.
43185             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
43186             if (me.floating) {
43187                 me.toFront(true);
43188             }
43189         }
43190         return me;
43191     },
43192
43193     /**
43194      * @private
43195      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
43196      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
43197      * by the {@link #focus} method.
43198      * @returns {Ext.Element} the focus holing element.
43199      */
43200     getFocusEl: function() {
43201         return this.el;
43202     },
43203
43204     // private
43205     blur: function() {
43206         if (this.rendered) {
43207             this.getFocusEl().blur();
43208         }
43209         return this;
43210     },
43211
43212     getEl: function() {
43213         return this.el;
43214     },
43215
43216     // Deprecate 5.0
43217     getResizeEl: function() {
43218         return this.el;
43219     },
43220
43221     // Deprecate 5.0
43222     getPositionEl: function() {
43223         return this.el;
43224     },
43225
43226     // Deprecate 5.0
43227     getActionEl: function() {
43228         return this.el;
43229     },
43230
43231     // Deprecate 5.0
43232     getVisibilityEl: function() {
43233         return this.el;
43234     },
43235
43236     // Deprecate 5.0
43237     onResize: Ext.emptyFn,
43238
43239     // private
43240     getBubbleTarget: function() {
43241         return this.ownerCt;
43242     },
43243
43244     // private
43245     getContentTarget: function() {
43246         return this.el;
43247     },
43248
43249     /**
43250      * Clone the current component using the original config values passed into this instance by default.
43251      * @param {Object} overrides A new config containing any properties to override in the cloned version.
43252      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
43253      * @return {Ext.Component} clone The cloned copy of this component
43254      */
43255     cloneConfig: function(overrides) {
43256         overrides = overrides || {};
43257         var id = overrides.id || Ext.id(),
43258             cfg = Ext.applyIf(overrides, this.initialConfig),
43259             self;
43260
43261         cfg.id = id;
43262
43263         self = Ext.getClass(this);
43264
43265         // prevent dup id
43266         return new self(cfg);
43267     },
43268
43269     /**
43270      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
43271      * xtypes, see the {@link Ext.Component} header. Example usage:
43272      *
43273      *     var t = new Ext.form.field.Text();
43274      *     alert(t.getXType());  // alerts 'textfield'
43275      *
43276      * @return {String} The xtype
43277      */
43278     getXType: function() {
43279         return this.self.xtype;
43280     },
43281
43282     /**
43283      * Find a container above this component at any level by a custom function. If the passed function returns true, the
43284      * container will be returned.
43285      * @param {Function} fn The custom function to call with the arguments (container, this component).
43286      * @return {Ext.container.Container} The first Container for which the custom function returns true
43287      */
43288     findParentBy: function(fn) {
43289         var p;
43290
43291         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
43292         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
43293         return p || null;
43294     },
43295
43296     /**
43297      * Find a container above this component at any level by xtype or class
43298      *
43299      * See also the {@link Ext.Component#up up} method.
43300      *
43301      * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
43302      * @return {Ext.container.Container} The first Container which matches the given xtype or class
43303      */
43304     findParentByType: function(xtype) {
43305         return Ext.isFunction(xtype) ?
43306             this.findParentBy(function(p) {
43307                 return p.constructor === xtype;
43308             })
43309         :
43310             this.up(xtype);
43311     },
43312
43313     /**
43314      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
43315      * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
43316      * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
43317      *
43318      * @param {Function} fn The function to call
43319      * @param {Object} [scope] The scope of the function. Defaults to current node.
43320      * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
43321      * @return {Ext.Component} this
43322      */
43323     bubble: function(fn, scope, args) {
43324         var p = this;
43325         while (p) {
43326             if (fn.apply(scope || p, args || [p]) === false) {
43327                 break;
43328             }
43329             p = p.ownerCt;
43330         }
43331         return this;
43332     },
43333
43334     getProxy: function() {
43335         var me = this,
43336             target;
43337
43338         if (!me.proxy) {
43339             target = Ext.getBody();
43340             if (Ext.scopeResetCSS) {
43341                 me.proxyWrap = target = Ext.getBody().createChild({
43342                     cls: Ext.baseCSSPrefix + 'reset'
43343                 });
43344             }
43345             me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
43346         }
43347         return me.proxy;
43348     }
43349
43350 });
43351
43352 /**
43353  * @class Ext.layout.container.AbstractContainer
43354  * @extends Ext.layout.Layout
43355  * Please refer to sub classes documentation
43356  * @private
43357  */
43358 Ext.define('Ext.layout.container.AbstractContainer', {
43359
43360     /* Begin Definitions */
43361
43362     extend: 'Ext.layout.Layout',
43363
43364     /* End Definitions */
43365
43366     type: 'container',
43367
43368     /**
43369      * @cfg {Boolean} bindToOwnerCtComponent
43370      * Flag to notify the ownerCt Component on afterLayout of a change
43371      */
43372     bindToOwnerCtComponent: false,
43373
43374     /**
43375      * @cfg {Boolean} bindToOwnerCtContainer
43376      * Flag to notify the ownerCt Container on afterLayout of a change
43377      */
43378     bindToOwnerCtContainer: false,
43379
43380     /**
43381      * @cfg {String} itemCls
43382      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
43383      * customized styles to the container or any of its children using standard CSS rules. See
43384      * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
43385      * </p>
43386      */
43387
43388     /**
43389     * Set the size of an item within the Container.  We should always use setCalculatedSize.
43390     * @private
43391     */
43392     setItemSize: function(item, width, height) {
43393         if (Ext.isObject(width)) {
43394             height = width.height;
43395             width = width.width;
43396         }
43397         item.setCalculatedSize(width, height, this.owner);
43398     },
43399
43400     /**
43401      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
43402      * base class), or the layout phase (onLayout).</p>
43403      * @return {Ext.Component[]} of child components
43404      */
43405     getLayoutItems: function() {
43406         return this.owner && this.owner.items && this.owner.items.items || [];
43407     },
43408
43409     /**
43410      * Containers should not lay out child components when collapsed.
43411      */
43412     beforeLayout: function() {
43413         return !this.owner.collapsed && this.callParent(arguments);
43414     },
43415
43416     afterLayout: function() {
43417         this.owner.afterLayout(this);
43418     },
43419     /**
43420      * Returns the owner component's resize element.
43421      * @return {Ext.Element}
43422      */
43423      getTarget: function() {
43424          return this.owner.getTargetEl();
43425      },
43426     /**
43427      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
43428      * May be overridden in layout managers which implement an inner element.
43429      * @return {Ext.Element}
43430      */
43431      getRenderTarget: function() {
43432          return this.owner.getTargetEl();
43433      }
43434 });
43435
43436 /**
43437 * @class Ext.layout.container.Container
43438 * @extends Ext.layout.container.AbstractContainer
43439 * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
43440 * configuration property.  See {@link Ext.container.Container#layout} for additional details.</p>
43441 */
43442 Ext.define('Ext.layout.container.Container', {
43443
43444     /* Begin Definitions */
43445
43446     extend: 'Ext.layout.container.AbstractContainer',
43447     alternateClassName: 'Ext.layout.ContainerLayout',
43448
43449     /* End Definitions */
43450
43451     layoutItem: function(item, box) {
43452         if (box) {
43453             item.doComponentLayout(box.width, box.height);
43454         } else {
43455             item.doComponentLayout();
43456         }
43457     },
43458
43459     getLayoutTargetSize : function() {
43460         var target = this.getTarget(),
43461             ret;
43462
43463         if (target) {
43464             ret = target.getViewSize();
43465
43466             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
43467             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
43468             // with getViewSize
43469             if (Ext.isIE && ret.width == 0){
43470                 ret = target.getStyleSize();
43471             }
43472
43473             ret.width -= target.getPadding('lr');
43474             ret.height -= target.getPadding('tb');
43475         }
43476         return ret;
43477     },
43478
43479     beforeLayout: function() {
43480         if (this.owner.beforeLayout(arguments) !== false) {
43481             return this.callParent(arguments);
43482         }
43483         else {
43484             return false;
43485         }
43486     },
43487
43488     /**
43489      * @protected
43490      * Returns all items that are rendered
43491      * @return {Array} All matching items
43492      */
43493     getRenderedItems: function() {
43494         var me = this,
43495             target = me.getTarget(),
43496             items = me.getLayoutItems(),
43497             ln = items.length,
43498             renderedItems = [],
43499             i, item;
43500
43501         for (i = 0; i < ln; i++) {
43502             item = items[i];
43503             if (item.rendered && me.isValidParent(item, target, i)) {
43504                 renderedItems.push(item);
43505             }
43506         }
43507
43508         return renderedItems;
43509     },
43510
43511     /**
43512      * @protected
43513      * Returns all items that are both rendered and visible
43514      * @return {Array} All matching items
43515      */
43516     getVisibleItems: function() {
43517         var target   = this.getTarget(),
43518             items = this.getLayoutItems(),
43519             ln = items.length,
43520             visibleItems = [],
43521             i, item;
43522
43523         for (i = 0; i < ln; i++) {
43524             item = items[i];
43525             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
43526                 visibleItems.push(item);
43527             }
43528         }
43529
43530         return visibleItems;
43531     }
43532 });
43533 /**
43534  * @class Ext.layout.container.Auto
43535  * @extends Ext.layout.container.Container
43536  *
43537  * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
43538  * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
43539  * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
43540  * to any child containers.
43541  *
43542  *     @example
43543  *     Ext.create('Ext.Panel', {
43544  *         width: 500,
43545  *         height: 280,
43546  *         title: "AutoLayout Panel",
43547  *         layout: 'auto',
43548  *         renderTo: document.body,
43549  *         items: [{
43550  *             xtype: 'panel',
43551  *             title: 'Top Inner Panel',
43552  *             width: '75%',
43553  *             height: 90
43554  *         },
43555  *         {
43556  *             xtype: 'panel',
43557  *             title: 'Bottom Inner Panel',
43558  *             width: '75%',
43559  *             height: 90
43560  *         }]
43561  *     });
43562  */
43563 Ext.define('Ext.layout.container.Auto', {
43564
43565     /* Begin Definitions */
43566
43567     alias: ['layout.auto', 'layout.autocontainer'],
43568
43569     extend: 'Ext.layout.container.Container',
43570
43571     /* End Definitions */
43572
43573     type: 'autocontainer',
43574
43575     bindToOwnerCtComponent: true,
43576
43577     // @private
43578     onLayout : function(owner, target) {
43579         var me = this,
43580             items = me.getLayoutItems(),
43581             ln = items.length,
43582             i;
43583
43584         // Ensure the Container is only primed with the clear element if there are child items.
43585         if (ln) {
43586             // Auto layout uses natural HTML flow to arrange the child items.
43587             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
43588             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
43589             if (!me.clearEl) {
43590                 me.clearEl = me.getRenderTarget().createChild({
43591                     cls: Ext.baseCSSPrefix + 'clear',
43592                     role: 'presentation'
43593                 });
43594             }
43595
43596             // Auto layout allows CSS to size its child items.
43597             for (i = 0; i < ln; i++) {
43598                 me.setItemSize(items[i]);
43599             }
43600         }
43601     },
43602
43603     configureItem: function(item) {
43604         this.callParent(arguments);
43605
43606         // Auto layout does not manage any dimensions.
43607         item.layoutManagedHeight = 2;
43608         item.layoutManagedWidth = 2;
43609     }
43610 });
43611 /**
43612  * @class Ext.container.AbstractContainer
43613  * @extends Ext.Component
43614  * An abstract base class which provides shared methods for Containers across the Sencha product line.
43615  * @private
43616  */
43617 Ext.define('Ext.container.AbstractContainer', {
43618
43619     /* Begin Definitions */
43620
43621     extend: 'Ext.Component',
43622
43623     requires: [
43624         'Ext.util.MixedCollection',
43625         'Ext.layout.container.Auto',
43626         'Ext.ZIndexManager'
43627     ],
43628
43629     /* End Definitions */
43630     /**
43631      * @cfg {String/Object} layout
43632      * <p><b>Important</b>: In order for child items to be correctly sized and
43633      * positioned, typically a layout manager <b>must</b> be specified through
43634      * the <code>layout</code> configuration option.</p>
43635      * <p>The sizing and positioning of child {@link #items} is the responsibility of
43636      * the Container's layout manager which creates and manages the type of layout
43637      * you have in mind.  For example:</p>
43638      * <p>If the {@link #layout} configuration is not explicitly specified for
43639      * a general purpose container (e.g. Container or Panel) the
43640      * {@link Ext.layout.container.Auto default layout manager} will be used
43641      * which does nothing but render child components sequentially into the
43642      * Container (no sizing or positioning will be performed in this situation).</p>
43643      * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
43644      * <div><ul class="mdetail-params">
43645      * <li><u>Specify as an Object</u></li>
43646      * <div><ul class="mdetail-params">
43647      * <li>Example usage:</li>
43648      * <pre><code>
43649 layout: {
43650     type: 'vbox',
43651     align: 'left'
43652 }
43653        </code></pre>
43654      *
43655      * <li><code><b>type</b></code></li>
43656      * <br/><p>The layout type to be used for this container.  If not specified,
43657      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
43658      * <p>Valid layout <code>type</code> values are:</p>
43659      * <div class="sub-desc"><ul class="mdetail-params">
43660      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
43661      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
43662      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
43663      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
43664      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
43665      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
43666      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
43667      * </ul></div>
43668      *
43669      * <li>Layout specific configuration properties</li>
43670      * <p>Additional layout specific configuration properties may also be
43671      * specified. For complete details regarding the valid config options for
43672      * each layout type, see the layout class corresponding to the <code>type</code>
43673      * specified.</p>
43674      *
43675      * </ul></div>
43676      *
43677      * <li><u>Specify as a String</u></li>
43678      * <div><ul class="mdetail-params">
43679      * <li>Example usage:</li>
43680      * <pre><code>
43681 layout: 'vbox'
43682        </code></pre>
43683      * <li><code><b>layout</b></code></li>
43684      * <p>The layout <code>type</code> to be used for this container (see list
43685      * of valid layout type values above).</p>
43686      * <p>Additional layout specific configuration properties. For complete
43687      * details regarding the valid config options for each layout type, see the
43688      * layout class corresponding to the <code>layout</code> specified.</p>
43689      * </ul></div></ul></div>
43690      */
43691
43692     /**
43693      * @cfg {String/Number} activeItem
43694      * A string component id or the numeric index of the component that should be initially activated within the
43695      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
43696      * item in the container's collection).  activeItem only applies to layout styles that can display
43697      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
43698      */
43699     /**
43700      * @cfg {Object/Object[]} items
43701      * <p>A single item, or an array of child Components to be added to this container</p>
43702      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
43703      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
43704      * <p>Example:</p>
43705      * <pre><code>
43706 // specifying a single item
43707 items: {...},
43708 layout: 'fit',    // The single items is sized to fit
43709
43710 // specifying multiple items
43711 items: [{...}, {...}],
43712 layout: 'hbox', // The items are arranged horizontally
43713        </code></pre>
43714      * <p>Each item may be:</p>
43715      * <ul>
43716      * <li>A {@link Ext.Component Component}</li>
43717      * <li>A Component configuration object</li>
43718      * </ul>
43719      * <p>If a configuration object is specified, the actual type of Component to be
43720      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
43721      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
43722      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
43723      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
43724      * <p><b>Notes</b>:</p>
43725      * <p>Ext uses lazy rendering. Child Components will only be rendered
43726      * should it become necessary. Items are automatically laid out when they are first
43727      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
43728      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
43729      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
43730      */
43731     /**
43732      * @cfg {Object/Function} defaults
43733      * This option is a means of applying default settings to all added items whether added through the {@link #items}
43734      * config or via the {@link #add} or {@link #insert} methods.
43735      *
43736      * Defaults are applied to both config objects and instantiated components conditionally so as not to override
43737      * existing properties in the item (see {@link Ext#applyIf}).
43738      *
43739      * If the defaults option is specified as a function, then the function will be called using this Container as the
43740      * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
43741      * from that call is then applied to the item as default properties.
43742      *
43743      * For example, to automatically apply padding to the body of each of a set of
43744      * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
43745      *
43746      * Usage:
43747      *
43748      *     defaults: { // defaults are applied to items, not the container
43749      *         autoScroll: true
43750      *     },
43751      *     items: [
43752      *         // default will not be applied here, panel1 will be autoScroll: false
43753      *         {
43754      *             xtype: 'panel',
43755      *             id: 'panel1',
43756      *             autoScroll: false
43757      *         },
43758      *         // this component will have autoScroll: true
43759      *         new Ext.panel.Panel({
43760      *             id: 'panel2'
43761      *         })
43762      *     ]
43763      */
43764
43765     /** @cfg {Boolean} suspendLayout
43766      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
43767      * as multiple arguments or an array.
43768      */
43769     suspendLayout : false,
43770
43771     /** @cfg {Boolean} autoDestroy
43772      * If true the container will automatically destroy any contained component that is removed from it, else
43773      * destruction must be handled manually.
43774      * Defaults to true.
43775      */
43776     autoDestroy : true,
43777
43778      /** @cfg {String} defaultType
43779       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
43780       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
43781       * <p>Defaults to <code>'panel'</code>.</p>
43782       */
43783     defaultType: 'panel',
43784
43785     isContainer : true,
43786
43787     /**
43788      * The number of container layout calls made on this object.
43789      * @property layoutCounter
43790      * @type {Number}
43791      * @private
43792      */
43793     layoutCounter : 0,
43794
43795     baseCls: Ext.baseCSSPrefix + 'container',
43796
43797     /**
43798      * @cfg {String[]} bubbleEvents
43799      * <p>An array of events that, when fired, should be bubbled to any parent container.
43800      * See {@link Ext.util.Observable#enableBubble}.
43801      * Defaults to <code>['add', 'remove']</code>.
43802      */
43803     bubbleEvents: ['add', 'remove'],
43804
43805     // @private
43806     initComponent : function(){
43807         var me = this;
43808         me.addEvents(
43809             /**
43810              * @event afterlayout
43811              * Fires when the components in this container are arranged by the associated layout manager.
43812              * @param {Ext.container.Container} this
43813              * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
43814              */
43815             'afterlayout',
43816             /**
43817              * @event beforeadd
43818              * Fires before any {@link Ext.Component} is added or inserted into the container.
43819              * A handler can return false to cancel the add.
43820              * @param {Ext.container.Container} this
43821              * @param {Ext.Component} component The component being added
43822              * @param {Number} index The index at which the component will be added to the container's items collection
43823              */
43824             'beforeadd',
43825             /**
43826              * @event beforeremove
43827              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
43828              * false to cancel the remove.
43829              * @param {Ext.container.Container} this
43830              * @param {Ext.Component} component The component being removed
43831              */
43832             'beforeremove',
43833             /**
43834              * @event add
43835              * @bubbles
43836              * Fires after any {@link Ext.Component} is added or inserted into the container.
43837              * @param {Ext.container.Container} this
43838              * @param {Ext.Component} component The component that was added
43839              * @param {Number} index The index at which the component was added to the container's items collection
43840              */
43841             'add',
43842             /**
43843              * @event remove
43844              * @bubbles
43845              * Fires after any {@link Ext.Component} is removed from the container.
43846              * @param {Ext.container.Container} this
43847              * @param {Ext.Component} component The component that was removed
43848              */
43849             'remove'
43850         );
43851
43852         // layoutOnShow stack
43853         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
43854         me.callParent();
43855         me.initItems();
43856     },
43857
43858     // @private
43859     initItems : function() {
43860         var me = this,
43861             items = me.items;
43862
43863         /**
43864          * The MixedCollection containing all the child items of this container.
43865          * @property items
43866          * @type Ext.util.MixedCollection
43867          */
43868         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
43869
43870         if (items) {
43871             if (!Ext.isArray(items)) {
43872                 items = [items];
43873             }
43874
43875             me.add(items);
43876         }
43877     },
43878
43879     // @private
43880     afterRender : function() {
43881         this.getLayout();
43882         this.callParent();
43883     },
43884
43885     renderChildren: function () {
43886         var me = this,
43887             layout = me.getLayout();
43888
43889         me.callParent();
43890         // this component's elements exist, so now create the child components' elements
43891
43892         if (layout) {
43893             me.suspendLayout = true;
43894             layout.renderChildren();
43895             delete me.suspendLayout;
43896         }
43897     },
43898
43899     // @private
43900     setLayout : function(layout) {
43901         var currentLayout = this.layout;
43902
43903         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
43904             currentLayout.setOwner(null);
43905         }
43906
43907         this.layout = layout;
43908         layout.setOwner(this);
43909     },
43910
43911     /**
43912      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
43913      * If a layout has not been instantiated yet, that is done first
43914      * @return {Ext.layout.container.AbstractContainer} The layout
43915      */
43916     getLayout : function() {
43917         var me = this;
43918         if (!me.layout || !me.layout.isLayout) {
43919             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
43920         }
43921
43922         return me.layout;
43923     },
43924
43925     /**
43926      * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
43927      * form most cases.
43928      * @return {Ext.container.Container} this
43929      */
43930     doLayout : function() {
43931         var me = this,
43932             layout = me.getLayout();
43933
43934         if (me.rendered && layout && !me.suspendLayout) {
43935             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
43936             if (!me.isFixedWidth() || !me.isFixedHeight()) {
43937                 // Only run the ComponentLayout if it is not already in progress
43938                 if (me.componentLayout.layoutBusy !== true) {
43939                     me.doComponentLayout();
43940                     if (me.componentLayout.layoutCancelled === true) {
43941                         layout.layout();
43942                     }
43943                 }
43944             }
43945             // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
43946             else {
43947                 // Only run the ContainerLayout if it is not already in progress
43948                 if (layout.layoutBusy !== true) {
43949                     layout.layout();
43950                 }
43951             }
43952         }
43953
43954         return me;
43955     },
43956
43957     // @private
43958     afterLayout : function(layout) {
43959         ++this.layoutCounter;
43960         this.fireEvent('afterlayout', this, layout);
43961     },
43962
43963     // @private
43964     prepareItems : function(items, applyDefaults) {
43965         if (!Ext.isArray(items)) {
43966             items = [items];
43967         }
43968
43969         // Make sure defaults are applied and item is initialized
43970         var i = 0,
43971             len = items.length,
43972             item;
43973
43974         for (; i < len; i++) {
43975             item = items[i];
43976             if (applyDefaults) {
43977                 item = this.applyDefaults(item);
43978             }
43979             items[i] = this.lookupComponent(item);
43980         }
43981         return items;
43982     },
43983
43984     // @private
43985     applyDefaults : function(config) {
43986         var defaults = this.defaults;
43987
43988         if (defaults) {
43989             if (Ext.isFunction(defaults)) {
43990                 defaults = defaults.call(this, config);
43991             }
43992
43993             if (Ext.isString(config)) {
43994                 config = Ext.ComponentManager.get(config);
43995             }
43996             Ext.applyIf(config, defaults);
43997         }
43998
43999         return config;
44000     },
44001
44002     // @private
44003     lookupComponent : function(comp) {
44004         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
44005     },
44006
44007     // @private
44008     createComponent : function(config, defaultType) {
44009         // // add in ownerCt at creation time but then immediately
44010         // // remove so that onBeforeAdd can handle it
44011         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
44012         //
44013         // delete component.initialConfig.ownerCt;
44014         // delete component.ownerCt;
44015
44016         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
44017     },
44018
44019     // @private - used as the key lookup function for the items collection
44020     getComponentId : function(comp) {
44021         return comp.getItemId();
44022     },
44023
44024     /**
44025
44026 Adds {@link Ext.Component Component}(s) to this Container.
44027
44028 ##Description:##
44029
44030 - Fires the {@link #beforeadd} event before adding.
44031 - The Container's {@link #defaults default config values} will be applied
44032   accordingly (see `{@link #defaults}` for details).
44033 - Fires the `{@link #add}` event after the component has been added.
44034
44035 ##Notes:##
44036
44037 If the Container is __already rendered__ when `add`
44038 is called, it will render the newly added Component into its content area.
44039
44040 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
44041 will recalculate its internal layout at this time too.
44042
44043 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
44044
44045 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
44046
44047     tb = new {@link Ext.toolbar.Toolbar}({
44048         renderTo: document.body
44049     });  // toolbar is rendered
44050     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
44051
44052 ##Warning:##
44053
44054 Components directly managed by the BorderLayout layout manager
44055 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
44056 for more details.
44057
44058      * @param {Ext.Component[]/Ext.Component...} component
44059      * Either one or more Components to add or an Array of Components to add.
44060      * See `{@link #items}` for additional information.
44061      *
44062      * @return {Ext.Component[]/Ext.Component} The Components that were added.
44063      * @markdown
44064      */
44065     add : function() {
44066         var me = this,
44067             args = Array.prototype.slice.call(arguments),
44068             hasMultipleArgs,
44069             items,
44070             results = [],
44071             i,
44072             ln,
44073             item,
44074             index = -1,
44075             cmp;
44076
44077         if (typeof args[0] == 'number') {
44078             index = args.shift();
44079         }
44080
44081         hasMultipleArgs = args.length > 1;
44082         if (hasMultipleArgs || Ext.isArray(args[0])) {
44083
44084             items = hasMultipleArgs ? args : args[0];
44085             // Suspend Layouts while we add multiple items to the container
44086             me.suspendLayout = true;
44087             for (i = 0, ln = items.length; i < ln; i++) {
44088                 item = items[i];
44089
44090                 if (!item) {
44091                     Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
44092                 }
44093
44094                 if (index != -1) {
44095                     item = me.add(index + i, item);
44096                 } else {
44097                     item = me.add(item);
44098                 }
44099                 results.push(item);
44100             }
44101             // Resume Layouts now that all items have been added and do a single layout for all the items just added
44102             me.suspendLayout = false;
44103             me.doLayout();
44104             return results;
44105         }
44106
44107         cmp = me.prepareItems(args[0], true)[0];
44108
44109         // Floating Components are not added into the items collection
44110         // But they do get an upward ownerCt link so that they can traverse
44111         // up to their z-index parent.
44112         if (cmp.floating) {
44113             cmp.onAdded(me, index);
44114         } else {
44115             index = (index !== -1) ? index : me.items.length;
44116             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
44117                 me.items.insert(index, cmp);
44118                 cmp.onAdded(me, index);
44119                 me.onAdd(cmp, index);
44120                 me.fireEvent('add', me, cmp, index);
44121             }
44122             me.doLayout();
44123         }
44124         return cmp;
44125     },
44126
44127     onAdd : Ext.emptyFn,
44128     onRemove : Ext.emptyFn,
44129
44130     /**
44131      * Inserts a Component into this Container at a specified index. Fires the
44132      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
44133      * Component has been inserted.
44134      * @param {Number} index The index at which the Component will be inserted
44135      * into the Container's items collection
44136      * @param {Ext.Component} component The child Component to insert.<br><br>
44137      * Ext uses lazy rendering, and will only render the inserted Component should
44138      * it become necessary.<br><br>
44139      * A Component config object may be passed in order to avoid the overhead of
44140      * constructing a real Component object if lazy rendering might mean that the
44141      * inserted Component will not be rendered immediately. To take advantage of
44142      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
44143      * property to the registered type of the Component wanted.<br><br>
44144      * For a list of all available xtypes, see {@link Ext.Component}.
44145      * @return {Ext.Component} component The Component (or config object) that was
44146      * inserted with the Container's default config values applied.
44147      */
44148     insert : function(index, comp) {
44149         return this.add(index, comp);
44150     },
44151
44152     /**
44153      * Moves a Component within the Container
44154      * @param {Number} fromIdx The index the Component you wish to move is currently at.
44155      * @param {Number} toIdx The new index for the Component.
44156      * @return {Ext.Component} component The Component (or config object) that was moved.
44157      */
44158     move : function(fromIdx, toIdx) {
44159         var items = this.items,
44160             item;
44161         item = items.removeAt(fromIdx);
44162         if (item === false) {
44163             return false;
44164         }
44165         items.insert(toIdx, item);
44166         this.doLayout();
44167         return item;
44168     },
44169
44170     // @private
44171     onBeforeAdd : function(item) {
44172         var me = this;
44173
44174         if (item.ownerCt) {
44175             item.ownerCt.remove(item, false);
44176         }
44177
44178         if (me.border === false || me.border === 0) {
44179             item.border = (item.border === true);
44180         }
44181     },
44182
44183     /**
44184      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
44185      * the {@link #remove} event after the component has been removed.
44186      * @param {Ext.Component/String} component The component reference or id to remove.
44187      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
44188      * Defaults to the value of this Container's {@link #autoDestroy} config.
44189      * @return {Ext.Component} component The Component that was removed.
44190      */
44191     remove : function(comp, autoDestroy) {
44192         var me = this,
44193             c = me.getComponent(comp);
44194             if (Ext.isDefined(Ext.global.console) && !c) {
44195                 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.");
44196             }
44197
44198         if (c && me.fireEvent('beforeremove', me, c) !== false) {
44199             me.doRemove(c, autoDestroy);
44200             me.fireEvent('remove', me, c);
44201         }
44202
44203         return c;
44204     },
44205
44206     // @private
44207     doRemove : function(component, autoDestroy) {
44208         var me = this,
44209             layout = me.layout,
44210             hasLayout = layout && me.rendered;
44211
44212         me.items.remove(component);
44213         component.onRemoved();
44214
44215         if (hasLayout) {
44216             layout.onRemove(component);
44217         }
44218
44219         me.onRemove(component, autoDestroy);
44220
44221         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
44222             component.destroy();
44223         }
44224
44225         if (hasLayout && !autoDestroy) {
44226             layout.afterRemove(component);
44227         }
44228
44229         if (!me.destroying) {
44230             me.doLayout();
44231         }
44232     },
44233
44234     /**
44235      * Removes all components from this container.
44236      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
44237      * Defaults to the value of this Container's {@link #autoDestroy} config.
44238      * @return {Ext.Component[]} Array of the destroyed components
44239      */
44240     removeAll : function(autoDestroy) {
44241         var me = this,
44242             removeItems = me.items.items.slice(),
44243             items = [],
44244             i = 0,
44245             len = removeItems.length,
44246             item;
44247
44248         // Suspend Layouts while we remove multiple items from the container
44249         me.suspendLayout = true;
44250         for (; i < len; i++) {
44251             item = removeItems[i];
44252             me.remove(item, autoDestroy);
44253
44254             if (item.ownerCt !== me) {
44255                 items.push(item);
44256             }
44257         }
44258
44259         // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
44260         me.suspendLayout = false;
44261         if (len) {
44262             me.doLayout();
44263         }
44264         return items;
44265     },
44266
44267     // Used by ComponentQuery to retrieve all of the items
44268     // which can potentially be considered a child of this Container.
44269     // This should be overriden by components which have child items
44270     // that are not contained in items. For example dockedItems, menu, etc
44271     // IMPORTANT note for maintainers:
44272     //  Items are returned in tree traversal order. Each item is appended to the result array
44273     //  followed by the results of that child's getRefItems call.
44274     //  Floating child items are appended after internal child items.
44275     getRefItems : function(deep) {
44276         var me = this,
44277             items = me.items.items,
44278             len = items.length,
44279             i = 0,
44280             item,
44281             result = [];
44282
44283         for (; i < len; i++) {
44284             item = items[i];
44285             result.push(item);
44286             if (deep && item.getRefItems) {
44287                 result.push.apply(result, item.getRefItems(true));
44288             }
44289         }
44290
44291         // Append floating items to the list.
44292         // These will only be present after they are rendered.
44293         if (me.floatingItems && me.floatingItems.accessList) {
44294             result.push.apply(result, me.floatingItems.accessList);
44295         }
44296
44297         return result;
44298     },
44299
44300     /**
44301      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
44302      * each component. The scope (<code>this</code> reference) of the
44303      * function call will be the scope provided or the current component. The arguments to the function
44304      * will be the args provided or the current component. If the function returns false at any point,
44305      * the cascade is stopped on that branch.
44306      * @param {Function} fn The function to call
44307      * @param {Object} [scope] The scope of the function (defaults to current component)
44308      * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
44309      * @return {Ext.Container} this
44310      */
44311     cascade : function(fn, scope, origArgs){
44312         var me = this,
44313             cs = me.items ? me.items.items : [],
44314             len = cs.length,
44315             i = 0,
44316             c,
44317             args = origArgs ? origArgs.concat(me) : [me],
44318             componentIndex = args.length - 1;
44319
44320         if (fn.apply(scope || me, args) !== false) {
44321             for(; i < len; i++){
44322                 c = cs[i];
44323                 if (c.cascade) {
44324                     c.cascade(fn, scope, origArgs);
44325                 } else {
44326                     args[componentIndex] = c;
44327                     fn.apply(scope || cs, args);
44328                 }
44329             }
44330         }
44331         return this;
44332     },
44333
44334     /**
44335      * Examines this container's <code>{@link #items}</code> <b>property</b>
44336      * and gets a direct child component of this container.
44337      * @param {String/Number} comp This parameter may be any of the following:
44338      * <div><ul class="mdetail-params">
44339      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
44340      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
44341      * <li>a <b><code>Number</code></b> : representing the position of the child component
44342      * within the <code>{@link #items}</code> <b>property</b></li>
44343      * </ul></div>
44344      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
44345      * @return Ext.Component The component (if found).
44346      */
44347     getComponent : function(comp) {
44348         if (Ext.isObject(comp)) {
44349             comp = comp.getItemId();
44350         }
44351
44352         return this.items.get(comp);
44353     },
44354
44355     /**
44356      * Retrieves all descendant components which match the passed selector.
44357      * Executes an Ext.ComponentQuery.query using this container as its root.
44358      * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
44359      * If no selector is specified all items will be returned.
44360      * @return {Ext.Component[]} Components which matched the selector
44361      */
44362     query : function(selector) {
44363         selector = selector || '*';
44364         return Ext.ComponentQuery.query(selector, this);
44365     },
44366
44367     /**
44368      * Retrieves the first direct child of this container which matches the passed selector.
44369      * The passed in selector must comply with an Ext.ComponentQuery selector.
44370      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
44371      * specified, the first child will be returned.
44372      * @return Ext.Component
44373      */
44374     child : function(selector) {
44375         selector = selector || '';
44376         return this.query('> ' + selector)[0] || null;
44377     },
44378
44379     /**
44380      * Retrieves the first descendant of this container which matches the passed selector.
44381      * The passed in selector must comply with an Ext.ComponentQuery selector.
44382      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
44383      * specified, the first child will be returned.
44384      * @return Ext.Component
44385      */
44386     down : function(selector) {
44387         return this.query(selector)[0] || null;
44388     },
44389
44390     // inherit docs
44391     show : function() {
44392         this.callParent(arguments);
44393         this.performDeferredLayouts();
44394         return this;
44395     },
44396
44397     // Lay out any descendant containers who queued a layout operation during the time this was hidden
44398     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
44399     performDeferredLayouts: function() {
44400         var layoutCollection = this.layoutOnShow,
44401             ln = layoutCollection.getCount(),
44402             i = 0,
44403             needsLayout,
44404             item;
44405
44406         for (; i < ln; i++) {
44407             item = layoutCollection.get(i);
44408             needsLayout = item.needsLayout;
44409
44410             if (Ext.isObject(needsLayout)) {
44411                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
44412             }
44413         }
44414         layoutCollection.clear();
44415     },
44416
44417     //@private
44418     // Enable all immediate children that was previously disabled
44419     onEnable: function() {
44420         Ext.Array.each(this.query('[isFormField]'), function(item) {
44421             if (item.resetDisable) {
44422                 item.enable();
44423                 delete item.resetDisable;
44424             }
44425         });
44426         this.callParent();
44427     },
44428
44429     // @private
44430     // Disable all immediate children that was previously disabled
44431     onDisable: function() {
44432         Ext.Array.each(this.query('[isFormField]'), function(item) {
44433             if (item.resetDisable !== false && !item.disabled) {
44434                 item.disable();
44435                 item.resetDisable = true;
44436             }
44437         });
44438         this.callParent();
44439     },
44440
44441     /**
44442      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
44443      * from being executed.
44444      */
44445     beforeLayout: function() {
44446         return true;
44447     },
44448
44449     // @private
44450     beforeDestroy : function() {
44451         var me = this,
44452             items = me.items,
44453             c;
44454
44455         if (items) {
44456             while ((c = items.first())) {
44457                 me.doRemove(c, true);
44458             }
44459         }
44460
44461         Ext.destroy(
44462             me.layout
44463         );
44464         me.callParent();
44465     }
44466 });
44467
44468 /**
44469  * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
44470  * containing items, namely adding, inserting and removing items.
44471  *
44472  * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
44473  * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
44474  * lightweight Container to be encapsulated by an HTML element to your specifications by using the
44475  * {@link Ext.Component#autoEl autoEl} config option.
44476  *
44477  * The code below illustrates how to explicitly create a Container:
44478  *
44479  *     @example
44480  *     // Explicitly create a Container
44481  *     Ext.create('Ext.container.Container', {
44482  *         layout: {
44483  *             type: 'hbox'
44484  *         },
44485  *         width: 400,
44486  *         renderTo: Ext.getBody(),
44487  *         border: 1,
44488  *         style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
44489  *         defaults: {
44490  *             labelWidth: 80,
44491  *             // implicitly create Container by specifying xtype
44492  *             xtype: 'datefield',
44493  *             flex: 1,
44494  *             style: {
44495  *                 padding: '10px'
44496  *             }
44497  *         },
44498  *         items: [{
44499  *             xtype: 'datefield',
44500  *             name: 'startDate',
44501  *             fieldLabel: 'Start date'
44502  *         },{
44503  *             xtype: 'datefield',
44504  *             name: 'endDate',
44505  *             fieldLabel: 'End date'
44506  *         }]
44507  *     });
44508  *
44509  * ## Layout
44510  *
44511  * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
44512  * the Container using the `{@link #layout}` configuration property.
44513  *
44514  * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
44515  * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
44516  * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
44517  * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
44518  * inside the Container, and **does not apply any sizing** at all.
44519  *
44520  * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
44521  * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
44522  * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
44523  * any way when the Container is resized.
44524  *
44525  * Certain layout managers allow dynamic addition of child components. Those that do include
44526  * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
44527  * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
44528  *
44529  *     //  Create the GridPanel.
44530  *     var myNewGrid = new Ext.grid.Panel({
44531  *         store: myStore,
44532  *         headers: myHeaders,
44533  *         title: 'Results', // the title becomes the title of the tab
44534  *     });
44535  *
44536  *     myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
44537  *     myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
44538  *
44539  * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
44540  * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
44541  * Ext.layout.container.Fit fit} exactly into its client area.
44542  *
44543  * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
44544  * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
44545  * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
44546  * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
44547  * GridPanel will not be sized as expected.
44548  *
44549  * ## Adding via remote configuration
44550  *
44551  * A server side script can be used to add Components which are generated dynamically on the server. An example of
44552  * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
44553  *
44554  *     // execute an Ajax request to invoke server side script:
44555  *     Ext.Ajax.request({
44556  *         url: 'gen-invoice-grid.php',
44557  *         // send additional parameters to instruct server script
44558  *         params: {
44559  *             startDate: Ext.getCmp('start-date').getValue(),
44560  *             endDate: Ext.getCmp('end-date').getValue()
44561  *         },
44562  *         // process the response object to add it to the TabPanel:
44563  *         success: function(xhr) {
44564  *             var newComponent = eval(xhr.responseText); // see discussion below
44565  *             myTabPanel.add(newComponent); // add the component to the TabPanel
44566  *             myTabPanel.setActiveTab(newComponent);
44567  *         },
44568  *         failure: function() {
44569  *             Ext.Msg.alert("Grid create failed", "Server communication failure");
44570  *         }
44571  *     });
44572  *
44573  * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
44574  * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
44575  *
44576  *     {
44577  *         "xtype": 'grid',
44578  *         "title": 'Invoice Report',
44579  *         "store": {
44580  *             "model": 'Invoice',
44581  *             "proxy": {
44582  *                 "type": 'ajax',
44583  *                 "url": 'get-invoice-data.php',
44584  *                 "reader": {
44585  *                     "type": 'json'
44586  *                     "record": 'transaction',
44587  *                     "idProperty": 'id',
44588  *                     "totalRecords": 'total'
44589  *                 })
44590  *             },
44591  *             "autoLoad": {
44592  *                 "params": {
44593  *                     "startDate": '01/01/2008',
44594  *                     "endDate": '01/31/2008'
44595  *                 }
44596  *             }
44597  *         },
44598  *         "headers": [
44599  *             {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
44600  *             {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
44601  *             {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
44602  *             {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
44603  *         ]
44604  *     }
44605  *
44606  * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
44607  * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
44608  * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
44609  *
44610  * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
44611  * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
44612  * into the code since these are all known on the server.
44613  */
44614 Ext.define('Ext.container.Container', {
44615     extend: 'Ext.container.AbstractContainer',
44616     alias: 'widget.container',
44617     alternateClassName: 'Ext.Container',
44618
44619     /**
44620      * Return the immediate child Component in which the passed element is located.
44621      * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
44622      * @return {Ext.Component} The child item which contains the passed element.
44623      */
44624     getChildByElement: function(el) {
44625         var item,
44626             itemEl,
44627             i = 0,
44628             it = this.items.items,
44629             ln = it.length;
44630
44631         el = Ext.getDom(el);
44632         for (; i < ln; i++) {
44633             item = it[i];
44634             itemEl = item.getEl();
44635             if ((itemEl.dom === el) || itemEl.contains(el)) {
44636                 return item;
44637             }
44638         }
44639         return null;
44640     }
44641 });
44642
44643 /**
44644  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
44645  * the right-justified button container.
44646  *
44647  *     @example
44648  *     Ext.create('Ext.panel.Panel', {
44649  *          title: 'Toolbar Fill Example',
44650  *          width: 300,
44651  *          height: 200,
44652  *          tbar : [
44653  *              'Item 1',
44654  *              { xtype: 'tbfill' },
44655  *              'Item 2'
44656  *          ],
44657  *          renderTo: Ext.getBody()
44658  *      });
44659  */
44660 Ext.define('Ext.toolbar.Fill', {
44661     extend: 'Ext.Component',
44662     alias: 'widget.tbfill',
44663     alternateClassName: 'Ext.Toolbar.Fill',
44664     isFill : true,
44665     flex: 1
44666 });
44667 /**
44668  * @class Ext.toolbar.Item
44669  * @extends Ext.Component
44670  * The base class that other non-interacting Toolbar Item classes should extend in order to
44671  * get some basic common toolbar item functionality.
44672  */
44673 Ext.define('Ext.toolbar.Item', {
44674     extend: 'Ext.Component',
44675     alias: 'widget.tbitem',
44676     alternateClassName: 'Ext.Toolbar.Item',
44677     enable:Ext.emptyFn,
44678     disable:Ext.emptyFn,
44679     focus:Ext.emptyFn
44680     /**
44681      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
44682      */
44683 });
44684 /**
44685  * @class Ext.toolbar.Separator
44686  * @extends Ext.toolbar.Item
44687  * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
44688  *
44689  *     @example
44690  *     Ext.create('Ext.panel.Panel', {
44691  *         title: 'Toolbar Seperator Example',
44692  *         width: 300,
44693  *         height: 200,
44694  *         tbar : [
44695  *             'Item 1',
44696  *             { xtype: 'tbseparator' },
44697  *             'Item 2'
44698  *         ],
44699  *         renderTo: Ext.getBody()
44700  *     });
44701  */
44702 Ext.define('Ext.toolbar.Separator', {
44703     extend: 'Ext.toolbar.Item',
44704     alias: 'widget.tbseparator',
44705     alternateClassName: 'Ext.Toolbar.Separator',
44706     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
44707     focusable: false
44708 });
44709 /**
44710  * @class Ext.menu.Manager
44711  * Provides a common registry of all menus on a page.
44712  * @singleton
44713  */
44714 Ext.define('Ext.menu.Manager', {
44715     singleton: true,
44716     requires: [
44717         'Ext.util.MixedCollection',
44718         'Ext.util.KeyMap'
44719     ],
44720     alternateClassName: 'Ext.menu.MenuMgr',
44721
44722     uses: ['Ext.menu.Menu'],
44723
44724     menus: {},
44725     groups: {},
44726     attached: false,
44727     lastShow: new Date(),
44728
44729     init: function() {
44730         var me = this;
44731         
44732         me.active = Ext.create('Ext.util.MixedCollection');
44733         Ext.getDoc().addKeyListener(27, function() {
44734             if (me.active.length > 0) {
44735                 me.hideAll();
44736             }
44737         }, me);
44738     },
44739
44740     /**
44741      * Hides all menus that are currently visible
44742      * @return {Boolean} success True if any active menus were hidden.
44743      */
44744     hideAll: function() {
44745         var active = this.active,
44746             c;
44747         if (active && active.length > 0) {
44748             c = active.clone();
44749             c.each(function(m) {
44750                 m.hide();
44751             });
44752             return true;
44753         }
44754         return false;
44755     },
44756
44757     onHide: function(m) {
44758         var me = this,
44759             active = me.active;
44760         active.remove(m);
44761         if (active.length < 1) {
44762             Ext.getDoc().un('mousedown', me.onMouseDown, me);
44763             me.attached = false;
44764         }
44765     },
44766
44767     onShow: function(m) {
44768         var me = this,
44769             active   = me.active,
44770             last     = active.last(),
44771             attached = me.attached,
44772             menuEl   = m.getEl(),
44773             zIndex;
44774
44775         me.lastShow = new Date();
44776         active.add(m);
44777         if (!attached) {
44778             Ext.getDoc().on('mousedown', me.onMouseDown, me);
44779             me.attached = true;
44780         }
44781         m.toFront();
44782     },
44783
44784     onBeforeHide: function(m) {
44785         if (m.activeChild) {
44786             m.activeChild.hide();
44787         }
44788         if (m.autoHideTimer) {
44789             clearTimeout(m.autoHideTimer);
44790             delete m.autoHideTimer;
44791         }
44792     },
44793
44794     onBeforeShow: function(m) {
44795         var active = this.active,
44796             parentMenu = m.parentMenu;
44797             
44798         active.remove(m);
44799         if (!parentMenu && !m.allowOtherMenus) {
44800             this.hideAll();
44801         }
44802         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
44803             parentMenu.activeChild.hide();
44804         }
44805     },
44806
44807     // private
44808     onMouseDown: function(e) {
44809         var me = this,
44810             active = me.active,
44811             lastShow = me.lastShow,
44812             target = e.target;
44813
44814         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
44815             me.hideAll();
44816             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
44817             // fired on the element, so we'll focus it here
44818             if (Ext.isIE && Ext.fly(target).focusable()) {
44819                 target.focus();
44820             }
44821         }
44822     },
44823
44824     // private
44825     register: function(menu) {
44826         var me = this;
44827
44828         if (!me.active) {
44829             me.init();
44830         }
44831
44832         if (menu.floating) {
44833             me.menus[menu.id] = menu;
44834             menu.on({
44835                 beforehide: me.onBeforeHide,
44836                 hide: me.onHide,
44837                 beforeshow: me.onBeforeShow,
44838                 show: me.onShow,
44839                 scope: me
44840             });
44841         }
44842     },
44843
44844     /**
44845      * Returns a {@link Ext.menu.Menu} object
44846      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
44847      * be used to generate and return a new Menu this.
44848      * @return {Ext.menu.Menu} The specified menu, or null if none are found
44849      */
44850     get: function(menu) {
44851         var menus = this.menus;
44852         
44853         if (typeof menu == 'string') { // menu id
44854             if (!menus) {  // not initialized, no menus to return
44855                 return null;
44856             }
44857             return menus[menu];
44858         } else if (menu.isMenu) {  // menu instance
44859             return menu;
44860         } else if (Ext.isArray(menu)) { // array of menu items
44861             return Ext.create('Ext.menu.Menu', {items:menu});
44862         } else { // otherwise, must be a config
44863             return Ext.ComponentManager.create(menu, 'menu');
44864         }
44865     },
44866
44867     // private
44868     unregister: function(menu) {
44869         var me = this,
44870             menus = me.menus,
44871             active = me.active;
44872
44873         delete menus[menu.id];
44874         active.remove(menu);
44875         menu.un({
44876             beforehide: me.onBeforeHide,
44877             hide: me.onHide,
44878             beforeshow: me.onBeforeShow,
44879             show: me.onShow,
44880             scope: me
44881         });
44882     },
44883
44884     // private
44885     registerCheckable: function(menuItem) {
44886         var groups  = this.groups,
44887             groupId = menuItem.group;
44888
44889         if (groupId) {
44890             if (!groups[groupId]) {
44891                 groups[groupId] = [];
44892             }
44893
44894             groups[groupId].push(menuItem);
44895         }
44896     },
44897
44898     // private
44899     unregisterCheckable: function(menuItem) {
44900         var groups  = this.groups,
44901             groupId = menuItem.group;
44902
44903         if (groupId) {
44904             Ext.Array.remove(groups[groupId], menuItem);
44905         }
44906     },
44907
44908     onCheckChange: function(menuItem, state) {
44909         var groups  = this.groups,
44910             groupId = menuItem.group,
44911             i       = 0,
44912             group, ln, curr;
44913
44914         if (groupId && state) {
44915             group = groups[groupId];
44916             ln = group.length;
44917             for (; i < ln; i++) {
44918                 curr = group[i];
44919                 if (curr != menuItem) {
44920                     curr.setChecked(false);
44921                 }
44922             }
44923         }
44924     }
44925 });
44926 /**
44927  * Component layout for buttons
44928  * @class Ext.layout.component.Button
44929  * @extends Ext.layout.component.Component
44930  * @private
44931  */
44932 Ext.define('Ext.layout.component.Button', {
44933
44934     /* Begin Definitions */
44935
44936     alias: ['layout.button'],
44937
44938     extend: 'Ext.layout.component.Component',
44939
44940     /* End Definitions */
44941
44942     type: 'button',
44943
44944     cellClsRE: /-btn-(tl|br)\b/,
44945     htmlRE: /<.*>/,
44946
44947     beforeLayout: function() {
44948         return this.callParent(arguments) || this.lastText !== this.owner.text;
44949     },
44950
44951     /**
44952      * Set the dimensions of the inner &lt;button&gt; element to match the
44953      * component dimensions.
44954      */
44955     onLayout: function(width, height) {
44956         var me = this,
44957             isNum = Ext.isNumber,
44958             owner = me.owner,
44959             ownerEl = owner.el,
44960             btnEl = owner.btnEl,
44961             btnInnerEl = owner.btnInnerEl,
44962             btnIconEl = owner.btnIconEl,
44963             sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
44964             minWidth = owner.minWidth,
44965             maxWidth = owner.maxWidth,
44966             ownerWidth, btnFrameWidth, metrics;
44967
44968         me.getTargetInfo();
44969         me.callParent(arguments);
44970
44971         btnInnerEl.unclip();
44972         me.setTargetSize(width, height);
44973
44974         if (!isNum(width)) {
44975             // In IE7 strict mode button elements with width:auto get strange extra side margins within
44976             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
44977             // the size of the text and set the width to match.
44978             if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
44979                 btnFrameWidth = me.btnFrameWidth;
44980                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
44981                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
44982                 btnEl.setWidth(metrics.width + btnFrameWidth);
44983                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
44984
44985                 if (sizeIconEl) {
44986                     btnIconEl.setWidth(metrics.width + btnFrameWidth);
44987                 }
44988             } else {
44989                 // Remove any previous fixed widths
44990                 ownerEl.setWidth(null);
44991                 btnEl.setWidth(null);
44992                 btnInnerEl.setWidth(null);
44993                 btnIconEl.setWidth(null);
44994             }
44995
44996             // Handle maxWidth/minWidth config
44997             if (minWidth || maxWidth) {
44998                 ownerWidth = ownerEl.getWidth();
44999                 if (minWidth && (ownerWidth < minWidth)) {
45000                     me.setTargetSize(minWidth, height);
45001                 }
45002                 else if (maxWidth && (ownerWidth > maxWidth)) {
45003                     btnInnerEl.clip();
45004                     me.setTargetSize(maxWidth, height);
45005                 }
45006             }
45007         }
45008
45009         this.lastText = owner.text;
45010     },
45011
45012     setTargetSize: function(width, height) {
45013         var me = this,
45014             owner = me.owner,
45015             isNum = Ext.isNumber,
45016             btnInnerEl = owner.btnInnerEl,
45017             btnWidth = (isNum(width) ? width - me.adjWidth : width),
45018             btnHeight = (isNum(height) ? height - me.adjHeight : height),
45019             btnFrameHeight = me.btnFrameHeight,
45020             text = owner.getText(),
45021             textHeight;
45022
45023         me.callParent(arguments);
45024         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
45025         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
45026         if (btnHeight >= 0) {
45027             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
45028         }
45029
45030         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
45031         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
45032         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
45033         // vertically within the button's height. This is more expensive than the basic line-height approach so
45034         // we only do it if the text contains markup.
45035         if (text && this.htmlRE.test(text)) {
45036             btnInnerEl.setStyle('line-height', 'normal');
45037             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
45038             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
45039             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
45040         }
45041     },
45042
45043     getTargetInfo: function() {
45044         var me = this,
45045             owner = me.owner,
45046             ownerEl = owner.el,
45047             frameSize = me.frameSize,
45048             frameBody = owner.frameBody,
45049             btnWrap = owner.btnWrap,
45050             innerEl = owner.btnInnerEl;
45051
45052         if (!('adjWidth' in me)) {
45053             Ext.apply(me, {
45054                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
45055                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
45056                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
45057                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
45058                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
45059                 btnFrameWidth: innerEl.getFrameWidth('lr'),
45060                 btnFrameHeight: innerEl.getFrameWidth('tb'),
45061                 btnFrameTop: innerEl.getFrameWidth('t')
45062             });
45063         }
45064
45065         return me.callParent();
45066     }
45067 });
45068 /**
45069  * @docauthor Robert Dougan <rob@sencha.com>
45070  *
45071  * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
45072  * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
45073  * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
45074  * a user clicks the button, or use {@link #listeners listeners} for other events such as
45075  * {@link #mouseover mouseover}. Example usage:
45076  *
45077  *     @example
45078  *     Ext.create('Ext.Button', {
45079  *         text: 'Click me',
45080  *         renderTo: Ext.getBody(),
45081  *         handler: function() {
45082  *             alert('You clicked the button!')
45083  *         }
45084  *     });
45085  *
45086  * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
45087  * method.  Example usage:
45088  *
45089  *     @example
45090  *     Ext.create('Ext.Button', {
45091  *         text    : 'Dynamic Handler Button',
45092  *         renderTo: Ext.getBody(),
45093  *         handler : function() {
45094  *             // this button will spit out a different number every time you click it.
45095  *             // so firstly we must check if that number is already set:
45096  *             if (this.clickCount) {
45097  *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
45098  *                 this.clickCount++;
45099  *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
45100  *             } else {
45101  *                 // if the clickCount property is not set, we will set it and alert the user
45102  *                 this.clickCount = 1;
45103  *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
45104  *             }
45105  *         }
45106  *     });
45107  *
45108  * A button within a container:
45109  *
45110  *     @example
45111  *     Ext.create('Ext.Container', {
45112  *         renderTo: Ext.getBody(),
45113  *         items   : [
45114  *             {
45115  *                 xtype: 'button',
45116  *                 text : 'My Button'
45117  *             }
45118  *         ]
45119  *     });
45120  *
45121  * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
45122  *
45123  * - `'small'`
45124  * - `'medium'`
45125  * - `'large'`
45126  *
45127  * Example usage:
45128  *
45129  *     @example
45130  *     Ext.create('Ext.Button', {
45131  *         renderTo: document.body,
45132  *         text    : 'Click me',
45133  *         scale   : 'large'
45134  *     });
45135  *
45136  * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
45137  * Example usage:
45138  *
45139  *     @example
45140  *     Ext.create('Ext.Button', {
45141  *         renderTo: Ext.getBody(),
45142  *         text: 'Click Me',
45143  *         enableToggle: true
45144  *     });
45145  *
45146  * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
45147  * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
45148  * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
45149  * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
45150  * on button.  Example usage:
45151  *
45152  *     @example
45153  *     Ext.create('Ext.Button', {
45154  *         text      : 'Menu button',
45155  *         renderTo  : Ext.getBody(),
45156  *         arrowAlign: 'bottom',
45157  *         menu      : [
45158  *             {text: 'Item 1'},
45159  *             {text: 'Item 2'},
45160  *             {text: 'Item 3'},
45161  *             {text: 'Item 4'}
45162  *         ]
45163  *     });
45164  *
45165  * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
45166  * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
45167  *
45168  * - `click`
45169  * - `toggle`
45170  * - `mouseover`
45171  * - `mouseout`
45172  * - `mouseshow`
45173  * - `menuhide`
45174  * - `menutriggerover`
45175  * - `menutriggerout`
45176  *
45177  * Example usage:
45178  *
45179  *     @example
45180  *     Ext.create('Ext.Button', {
45181  *         text     : 'Button',
45182  *         renderTo : Ext.getBody(),
45183  *         listeners: {
45184  *             click: function() {
45185  *                 // this == the button, as we are in the local scope
45186  *                 this.setText('I was clicked!');
45187  *             },
45188  *             mouseover: function() {
45189  *                 // set a new config which says we moused over, if not already set
45190  *                 if (!this.mousedOver) {
45191  *                     this.mousedOver = true;
45192  *                     alert('You moused over a button!\n\nI wont do this again.');
45193  *                 }
45194  *             }
45195  *         }
45196  *     });
45197  */
45198 Ext.define('Ext.button.Button', {
45199
45200     /* Begin Definitions */
45201     alias: 'widget.button',
45202     extend: 'Ext.Component',
45203
45204     requires: [
45205         'Ext.menu.Manager',
45206         'Ext.util.ClickRepeater',
45207         'Ext.layout.component.Button',
45208         'Ext.util.TextMetrics',
45209         'Ext.util.KeyMap'
45210     ],
45211
45212     alternateClassName: 'Ext.Button',
45213     /* End Definitions */
45214
45215     isButton: true,
45216     componentLayout: 'button',
45217
45218     /**
45219      * @property {Boolean} hidden
45220      * True if this button is hidden. Read-only.
45221      */
45222     hidden: false,
45223
45224     /**
45225      * @property {Boolean} disabled
45226      * True if this button is disabled. Read-only.
45227      */
45228     disabled: false,
45229
45230     /**
45231      * @property {Boolean} pressed
45232      * True if this button is pressed (only if enableToggle = true). Read-only.
45233      */
45234     pressed: false,
45235
45236     /**
45237      * @cfg {String} text
45238      * The button text to be used as innerHTML (html tags are accepted).
45239      */
45240
45241     /**
45242      * @cfg {String} icon
45243      * The path to an image to display in the button (the image will be set as the background-image CSS property of the
45244      * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
45245      */
45246
45247     /**
45248      * @cfg {Function} handler
45249      * A function called when the button is clicked (can be used instead of click event).
45250      * @cfg {Ext.button.Button} handler.button This button.
45251      * @cfg {Ext.EventObject} handler.e The click event.
45252      */
45253
45254     /**
45255      * @cfg {Number} minWidth
45256      * The minimum width for this button (used to give a set of buttons a common width).
45257      * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
45258      */
45259
45260     /**
45261      * @cfg {String/Object} tooltip
45262      * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
45263      * QuickTips config object.
45264      */
45265
45266     /**
45267      * @cfg {Boolean} [hidden=false]
45268      * True to start hidden.
45269      */
45270
45271     /**
45272      * @cfg {Boolean} [disabled=true]
45273      * True to start disabled.
45274      */
45275
45276     /**
45277      * @cfg {Boolean} [pressed=false]
45278      * True to start pressed (only if enableToggle = true)
45279      */
45280
45281     /**
45282      * @cfg {String} toggleGroup
45283      * The group this toggle button is a member of (only 1 per group can be pressed)
45284      */
45285
45286     /**
45287      * @cfg {Boolean/Object} [repeat=false]
45288      * True to repeat fire the click event while the mouse is down. This can also be a
45289      * {@link Ext.util.ClickRepeater ClickRepeater} config object.
45290      */
45291
45292     /**
45293      * @cfg {Number} tabIndex
45294      * Set a DOM tabIndex for this button.
45295      */
45296
45297     /**
45298      * @cfg {Boolean} [allowDepress=true]
45299      * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
45300      */
45301
45302     /**
45303      * @cfg {Boolean} [enableToggle=false]
45304      * True to enable pressed/not pressed toggling.
45305      */
45306     enableToggle: false,
45307
45308     /**
45309      * @cfg {Function} toggleHandler
45310      * Function called when a Button with {@link #enableToggle} set to true is clicked.
45311      * @cfg {Ext.button.Button} toggleHandler.button This button.
45312      * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
45313      */
45314
45315     /**
45316      * @cfg {Ext.menu.Menu/String/Object} menu
45317      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
45318      */
45319
45320     /**
45321      * @cfg {String} menuAlign
45322      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
45323      */
45324     menuAlign: 'tl-bl?',
45325
45326     /**
45327      * @cfg {String} textAlign
45328      * The text alignment for this button (center, left, right).
45329      */
45330     textAlign: 'center',
45331
45332     /**
45333      * @cfg {String} overflowText
45334      * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
45335      * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
45336      */
45337
45338     /**
45339      * @cfg {String} iconCls
45340      * A css class which sets a background image to be used as the icon for this button.
45341      */
45342
45343     /**
45344      * @cfg {String} type
45345      * The type of `<input>` to create: submit, reset or button.
45346      */
45347     type: 'button',
45348
45349     /**
45350      * @cfg {String} clickEvent
45351      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
45352      */
45353     clickEvent: 'click',
45354
45355     /**
45356      * @cfg {Boolean} preventDefault
45357      * True to prevent the default action when the {@link #clickEvent} is processed.
45358      */
45359     preventDefault: true,
45360
45361     /**
45362      * @cfg {Boolean} handleMouseEvents
45363      * False to disable visual cues on mouseover, mouseout and mousedown.
45364      */
45365     handleMouseEvents: true,
45366
45367     /**
45368      * @cfg {String} tooltipType
45369      * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
45370      */
45371     tooltipType: 'qtip',
45372
45373     /**
45374      * @cfg {String} [baseCls='x-btn']
45375      * The base CSS class to add to all buttons.
45376      */
45377     baseCls: Ext.baseCSSPrefix + 'btn',
45378
45379     /**
45380      * @cfg {String} pressedCls
45381      * The CSS class to add to a button when it is in the pressed state.
45382      */
45383     pressedCls: 'pressed',
45384
45385     /**
45386      * @cfg {String} overCls
45387      * The CSS class to add to a button when it is in the over (hovered) state.
45388      */
45389     overCls: 'over',
45390
45391     /**
45392      * @cfg {String} focusCls
45393      * The CSS class to add to a button when it is in the focussed state.
45394      */
45395     focusCls: 'focus',
45396
45397     /**
45398      * @cfg {String} menuActiveCls
45399      * The CSS class to add to a button when it's menu is active.
45400      */
45401     menuActiveCls: 'menu-active',
45402
45403     /**
45404      * @cfg {String} href
45405      * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
45406      *
45407      *     handler: function() { window.location = "http://www.sencha.com" }
45408      */
45409
45410     /**
45411      * @cfg {Object} baseParams
45412      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
45413      */
45414
45415     /**
45416      * @cfg {Object} params
45417      * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
45418      * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
45419      */
45420
45421     ariaRole: 'button',
45422
45423     // inherited
45424     renderTpl:
45425         '<em id="{id}-btnWrap" class="{splitCls}">' +
45426             '<tpl if="href">' +
45427                 '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
45428                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
45429                         '{text}' +
45430                     '</span>' +
45431                         '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
45432                 '</a>' +
45433             '</tpl>' +
45434             '<tpl if="!href">' +
45435                 '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
45436                     // the autocomplete="off" is required to prevent Firefox from remembering
45437                     // the button's disabled state between page reloads.
45438                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
45439                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
45440                         '{text}' +
45441                     '</span>' +
45442                     '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
45443                 '</button>' +
45444             '</tpl>' +
45445         '</em>' ,
45446
45447     /**
45448      * @cfg {String} scale
45449      * The size of the Button. Three values are allowed:
45450      *
45451      * - 'small' - Results in the button element being 16px high.
45452      * - 'medium' - Results in the button element being 24px high.
45453      * - 'large' - Results in the button element being 32px high.
45454      */
45455     scale: 'small',
45456
45457     /**
45458      * @private
45459      * An array of allowed scales.
45460      */
45461     allowedScales: ['small', 'medium', 'large'],
45462
45463     /**
45464      * @cfg {Object} scope
45465      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
45466      * Defaults to this Button.
45467      */
45468
45469     /**
45470      * @cfg {String} iconAlign
45471      * The side of the Button box to render the icon. Four values are allowed:
45472      *
45473      * - 'top'
45474      * - 'right'
45475      * - 'bottom'
45476      * - 'left'
45477      */
45478     iconAlign: 'left',
45479
45480     /**
45481      * @cfg {String} arrowAlign
45482      * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
45483      * values are allowed:
45484      *
45485      * - 'right'
45486      * - 'bottom'
45487      */
45488     arrowAlign: 'right',
45489
45490     /**
45491      * @cfg {String} arrowCls
45492      * The className used for the inner arrow element if the button has a menu.
45493      */
45494     arrowCls: 'arrow',
45495
45496     /**
45497      * @property {Ext.Template} template
45498      * A {@link Ext.Template Template} used to create the Button's DOM structure.
45499      *
45500      * Instances, or subclasses which need a different DOM structure may provide a different template layout in
45501      * conjunction with an implementation of {@link #getTemplateArgs}.
45502      */
45503
45504     /**
45505      * @cfg {String} cls
45506      * A CSS class string to apply to the button's main element.
45507      */
45508
45509     /**
45510      * @property {Ext.menu.Menu} menu
45511      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
45512      * option.
45513      */
45514
45515     /**
45516      * @cfg {Boolean} autoWidth
45517      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
45518      * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
45519      * from doing this automatic sizing.
45520      */
45521
45522     maskOnDisable: false,
45523
45524     // inherit docs
45525     initComponent: function() {
45526         var me = this;
45527         me.callParent(arguments);
45528
45529         me.addEvents(
45530             /**
45531              * @event click
45532              * Fires when this button is clicked
45533              * @param {Ext.button.Button} this
45534              * @param {Event} e The click event
45535              */
45536             'click',
45537
45538             /**
45539              * @event toggle
45540              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
45541              * @param {Ext.button.Button} this
45542              * @param {Boolean} pressed
45543              */
45544             'toggle',
45545
45546             /**
45547              * @event mouseover
45548              * Fires when the mouse hovers over the button
45549              * @param {Ext.button.Button} this
45550              * @param {Event} e The event object
45551              */
45552             'mouseover',
45553
45554             /**
45555              * @event mouseout
45556              * Fires when the mouse exits the button
45557              * @param {Ext.button.Button} this
45558              * @param {Event} e The event object
45559              */
45560             'mouseout',
45561
45562             /**
45563              * @event menushow
45564              * If this button has a menu, this event fires when it is shown
45565              * @param {Ext.button.Button} this
45566              * @param {Ext.menu.Menu} menu
45567              */
45568             'menushow',
45569
45570             /**
45571              * @event menuhide
45572              * If this button has a menu, this event fires when it is hidden
45573              * @param {Ext.button.Button} this
45574              * @param {Ext.menu.Menu} menu
45575              */
45576             'menuhide',
45577
45578             /**
45579              * @event menutriggerover
45580              * If this button has a menu, this event fires when the mouse enters the menu triggering element
45581              * @param {Ext.button.Button} this
45582              * @param {Ext.menu.Menu} menu
45583              * @param {Event} e
45584              */
45585             'menutriggerover',
45586
45587             /**
45588              * @event menutriggerout
45589              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
45590              * @param {Ext.button.Button} this
45591              * @param {Ext.menu.Menu} menu
45592              * @param {Event} e
45593              */
45594             'menutriggerout'
45595         );
45596
45597         if (me.menu) {
45598             // Flag that we'll have a splitCls
45599             me.split = true;
45600
45601             // retrieve menu by id or instantiate instance if needed
45602             me.menu = Ext.menu.Manager.get(me.menu);
45603             me.menu.ownerCt = me;
45604         }
45605
45606         // Accept url as a synonym for href
45607         if (me.url) {
45608             me.href = me.url;
45609         }
45610
45611         // preventDefault defaults to false for links
45612         if (me.href && !me.hasOwnProperty('preventDefault')) {
45613             me.preventDefault = false;
45614         }
45615
45616         if (Ext.isString(me.toggleGroup)) {
45617             me.enableToggle = true;
45618         }
45619
45620     },
45621
45622     // private
45623     initAria: function() {
45624         this.callParent();
45625         var actionEl = this.getActionEl();
45626         if (this.menu) {
45627             actionEl.dom.setAttribute('aria-haspopup', true);
45628         }
45629     },
45630
45631     // inherit docs
45632     getActionEl: function() {
45633         return this.btnEl;
45634     },
45635
45636     // inherit docs
45637     getFocusEl: function() {
45638         return this.btnEl;
45639     },
45640
45641     // private
45642     setButtonCls: function() {
45643         var me = this,
45644             cls = [],
45645             btnIconEl = me.btnIconEl,
45646             hide = 'x-hide-display';
45647
45648         if (me.useSetClass) {
45649             if (!Ext.isEmpty(me.oldCls)) {
45650                 me.removeClsWithUI(me.oldCls);
45651                 me.removeClsWithUI(me.pressedCls);
45652             }
45653
45654             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
45655             if (me.iconCls || me.icon) {
45656                 if (me.text) {
45657                     cls.push('icon-text-' + me.iconAlign);
45658                 } else {
45659                     cls.push('icon');
45660                 }
45661                 if (btnIconEl) {
45662                     btnIconEl.removeCls(hide);
45663                 }
45664             } else {
45665                 if (me.text) {
45666                     cls.push('noicon');
45667                 }
45668                 if (btnIconEl) {
45669                     btnIconEl.addCls(hide);
45670                 }
45671             }
45672
45673             me.oldCls = cls;
45674             me.addClsWithUI(cls);
45675             me.addClsWithUI(me.pressed ? me.pressedCls : null);
45676         }
45677     },
45678
45679     // private
45680     onRender: function(ct, position) {
45681         // classNames for the button
45682         var me = this,
45683             repeater, btn;
45684
45685         // Apply the renderData to the template args
45686         Ext.applyIf(me.renderData, me.getTemplateArgs());
45687
45688         me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
45689
45690         if (me.scale) {
45691             me.ui = me.ui + '-' + me.scale;
45692         }
45693
45694         // Render internal structure
45695         me.callParent(arguments);
45696
45697         // If it is a split button + has a toolip for the arrow
45698         if (me.split && me.arrowTooltip) {
45699             me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
45700         }
45701
45702         // Add listeners to the focus and blur events on the element
45703         me.mon(me.btnEl, {
45704             scope: me,
45705             focus: me.onFocus,
45706             blur : me.onBlur
45707         });
45708
45709         // Set btn as a local variable for easy access
45710         btn = me.el;
45711
45712         if (me.icon) {
45713             me.setIcon(me.icon);
45714         }
45715
45716         if (me.iconCls) {
45717             me.setIconCls(me.iconCls);
45718         }
45719
45720         if (me.tooltip) {
45721             me.setTooltip(me.tooltip, true);
45722         }
45723
45724         if (me.textAlign) {
45725             me.setTextAlign(me.textAlign);
45726         }
45727
45728         // Add the mouse events to the button
45729         if (me.handleMouseEvents) {
45730             me.mon(btn, {
45731                 scope: me,
45732                 mouseover: me.onMouseOver,
45733                 mouseout: me.onMouseOut,
45734                 mousedown: me.onMouseDown
45735             });
45736
45737             if (me.split) {
45738                 me.mon(btn, {
45739                     mousemove: me.onMouseMove,
45740                     scope: me
45741                 });
45742             }
45743         }
45744
45745         // Check if the button has a menu
45746         if (me.menu) {
45747             me.mon(me.menu, {
45748                 scope: me,
45749                 show: me.onMenuShow,
45750                 hide: me.onMenuHide
45751             });
45752
45753             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
45754                 key: Ext.EventObject.DOWN,
45755                 handler: me.onDownKey,
45756                 scope: me
45757             });
45758         }
45759
45760         // Check if it is a repeat button
45761         if (me.repeat) {
45762             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
45763             me.mon(repeater, 'click', me.onRepeatClick, me);
45764         } else {
45765             me.mon(btn, me.clickEvent, me.onClick, me);
45766         }
45767
45768         // Register the button in the toggle manager
45769         Ext.ButtonToggleManager.register(me);
45770     },
45771
45772     /**
45773      * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
45774      * create this Button's DOM structure.
45775      *
45776      * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
45777      * their own implementation of this method.
45778      *
45779      * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
45780      * {@link #template} returns an Object containing the following properties:
45781      * @return {String} return.type The `<button>`'s {@link #type}
45782      * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
45783      * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
45784      * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
45785      * button's scale and icon alignment.
45786      * @return {String} return.text The {@link #text} to display ion the Button.
45787      * @return {Number} return.tabIndex The tab index within the input flow.
45788      */
45789     getTemplateArgs: function() {
45790         var me = this,
45791             persistentPadding = me.getPersistentBtnPadding(),
45792             innerSpanStyle = '';
45793
45794         // Create negative margin offsets to counteract persistent button padding if needed
45795         if (Math.max.apply(Math, persistentPadding) > 0) {
45796             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
45797                 return -pad + 'px';
45798             }).join(' ');
45799         }
45800
45801         return {
45802             href     : me.getHref(),
45803             target   : me.target || '_blank',
45804             type     : me.type,
45805             splitCls : me.getSplitCls(),
45806             cls      : me.cls,
45807             iconCls  : me.iconCls || '',
45808             text     : me.text || '&#160;',
45809             tabIndex : me.tabIndex,
45810             innerSpanStyle: innerSpanStyle
45811         };
45812     },
45813
45814     /**
45815      * @private
45816      * If there is a configured href for this Button, returns the href with parameters appended.
45817      * @returns The href string with parameters appended.
45818      */
45819     getHref: function() {
45820         var me = this,
45821             params = Ext.apply({}, me.baseParams);
45822
45823         // write baseParams first, then write any params
45824         params = Ext.apply(params, me.params);
45825         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
45826     },
45827
45828     /**
45829      * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
45830      *
45831      * **Only valid if the Button was originally configured with a {@link #href}**
45832      *
45833      * @param {Object} params Parameters to use in the href URL.
45834      */
45835     setParams: function(params) {
45836         this.params = params;
45837         this.btnEl.dom.href = this.getHref();
45838     },
45839
45840     getSplitCls: function() {
45841         var me = this;
45842         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
45843     },
45844
45845     // private
45846     afterRender: function() {
45847         var me = this;
45848         me.useSetClass = true;
45849         me.setButtonCls();
45850         me.doc = Ext.getDoc();
45851         this.callParent(arguments);
45852     },
45853
45854     /**
45855      * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
45856      * value of the {@link #iconCls} config internally.
45857      * @param {String} cls The CSS class providing the icon image
45858      * @return {Ext.button.Button} this
45859      */
45860     setIconCls: function(cls) {
45861         var me = this,
45862             btnIconEl = me.btnIconEl,
45863             oldCls = me.iconCls;
45864             
45865         me.iconCls = cls;
45866         if (btnIconEl) {
45867             // Remove the previous iconCls from the button
45868             btnIconEl.removeCls(oldCls);
45869             btnIconEl.addCls(cls || '');
45870             me.setButtonCls();
45871         }
45872         return me;
45873     },
45874
45875     /**
45876      * Sets the tooltip for this Button.
45877      *
45878      * @param {String/Object} tooltip This may be:
45879      *
45880      *   - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
45881      *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
45882      *
45883      * @return {Ext.button.Button} this
45884      */
45885     setTooltip: function(tooltip, initial) {
45886         var me = this;
45887
45888         if (me.rendered) {
45889             if (!initial) {
45890                 me.clearTip();
45891             }
45892             if (Ext.isObject(tooltip)) {
45893                 Ext.tip.QuickTipManager.register(Ext.apply({
45894                     target: me.btnEl.id
45895                 },
45896                 tooltip));
45897                 me.tooltip = tooltip;
45898             } else {
45899                 me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
45900             }
45901         } else {
45902             me.tooltip = tooltip;
45903         }
45904         return me;
45905     },
45906
45907     /**
45908      * Sets the text alignment for this button.
45909      * @param {String} align The new alignment of the button text. See {@link #textAlign}.
45910      */
45911     setTextAlign: function(align) {
45912         var me = this,
45913             btnEl = me.btnEl;
45914
45915         if (btnEl) {
45916             btnEl.removeCls(me.baseCls + '-' + me.textAlign);
45917             btnEl.addCls(me.baseCls + '-' + align);
45918         }
45919         me.textAlign = align;
45920         return me;
45921     },
45922
45923     getTipAttr: function(){
45924         return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
45925     },
45926
45927     // private
45928     getRefItems: function(deep){
45929         var menu = this.menu,
45930             items;
45931         
45932         if (menu) {
45933             items = menu.getRefItems(deep);
45934             items.unshift(menu);
45935         }
45936         return items || [];
45937     },
45938
45939     // private
45940     clearTip: function() {
45941         if (Ext.isObject(this.tooltip)) {
45942             Ext.tip.QuickTipManager.unregister(this.btnEl);
45943         }
45944     },
45945
45946     // private
45947     beforeDestroy: function() {
45948         var me = this;
45949         if (me.rendered) {
45950             me.clearTip();
45951         }
45952         if (me.menu && me.destroyMenu !== false) {
45953             Ext.destroy(me.menu);
45954         }
45955         Ext.destroy(me.btnInnerEl, me.repeater);
45956         me.callParent();
45957     },
45958
45959     // private
45960     onDestroy: function() {
45961         var me = this;
45962         if (me.rendered) {
45963             me.doc.un('mouseover', me.monitorMouseOver, me);
45964             me.doc.un('mouseup', me.onMouseUp, me);
45965             delete me.doc;
45966             Ext.ButtonToggleManager.unregister(me);
45967
45968             Ext.destroy(me.keyMap);
45969             delete me.keyMap;
45970         }
45971         me.callParent();
45972     },
45973
45974     /**
45975      * Assigns this Button's click handler
45976      * @param {Function} handler The function to call when the button is clicked
45977      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
45978      * Defaults to this Button.
45979      * @return {Ext.button.Button} this
45980      */
45981     setHandler: function(handler, scope) {
45982         this.handler = handler;
45983         this.scope = scope;
45984         return this;
45985     },
45986
45987     /**
45988      * Sets this Button's text
45989      * @param {String} text The button text
45990      * @return {Ext.button.Button} this
45991      */
45992     setText: function(text) {
45993         var me = this;
45994         me.text = text;
45995         if (me.el) {
45996             me.btnInnerEl.update(text || '&#160;');
45997             me.setButtonCls();
45998         }
45999         me.doComponentLayout();
46000         return me;
46001     },
46002
46003     /**
46004      * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
46005      * config internally.
46006      * @param {String} icon The path to an image to display in the button
46007      * @return {Ext.button.Button} this
46008      */
46009     setIcon: function(icon) {
46010         var me = this,
46011             iconEl = me.btnIconEl;
46012             
46013         me.icon = icon;
46014         if (iconEl) {
46015             iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
46016             me.setButtonCls();
46017         }
46018         return me;
46019     },
46020
46021     /**
46022      * Gets the text for this Button
46023      * @return {String} The button text
46024      */
46025     getText: function() {
46026         return this.text;
46027     },
46028
46029     /**
46030      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
46031      * @param {Boolean} [state] Force a particular state
46032      * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
46033      * @return {Ext.button.Button} this
46034      */
46035     toggle: function(state, suppressEvent) {
46036         var me = this;
46037         state = state === undefined ? !me.pressed : !!state;
46038         if (state !== me.pressed) {
46039             if (me.rendered) {
46040                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
46041             }
46042             me.btnEl.dom.setAttribute('aria-pressed', state);
46043             me.pressed = state;
46044             if (!suppressEvent) {
46045                 me.fireEvent('toggle', me, state);
46046                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
46047             }
46048         }
46049         return me;
46050     },
46051     
46052     maybeShowMenu: function(){
46053         var me = this;
46054         if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
46055             me.showMenu();
46056         }
46057     },
46058
46059     /**
46060      * Shows this button's menu (if it has one)
46061      */
46062     showMenu: function() {
46063         var me = this;
46064         if (me.rendered && me.menu) {
46065             if (me.tooltip && me.getTipAttr() != 'title') {
46066                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
46067             }
46068             if (me.menu.isVisible()) {
46069                 me.menu.hide();
46070             }
46071
46072             me.menu.showBy(me.el, me.menuAlign);
46073         }
46074         return me;
46075     },
46076
46077     /**
46078      * Hides this button's menu (if it has one)
46079      */
46080     hideMenu: function() {
46081         if (this.hasVisibleMenu()) {
46082             this.menu.hide();
46083         }
46084         return this;
46085     },
46086
46087     /**
46088      * Returns true if the button has a menu and it is visible
46089      * @return {Boolean}
46090      */
46091     hasVisibleMenu: function() {
46092         var menu = this.menu;
46093         return menu && menu.rendered && menu.isVisible();
46094     },
46095
46096     // private
46097     onRepeatClick: function(repeat, e) {
46098         this.onClick(e);
46099     },
46100
46101     // private
46102     onClick: function(e) {
46103         var me = this;
46104         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
46105             e.preventDefault();
46106         }
46107         if (e.button !== 0) {
46108             return;
46109         }
46110         if (!me.disabled) {
46111             me.doToggle();
46112             me.maybeShowMenu();
46113             me.fireHandler(e);
46114         }
46115     },
46116     
46117     fireHandler: function(e){
46118         var me = this,
46119             handler = me.handler;
46120             
46121         me.fireEvent('click', me, e);
46122         if (handler) {
46123             handler.call(me.scope || me, me, e);
46124         }
46125         me.onBlur();
46126     },
46127     
46128     doToggle: function(){
46129         var me = this;
46130         if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
46131             me.toggle();
46132         }
46133     },
46134
46135     /**
46136      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
46137      * The targets are interrogated to see what is being entered from where.
46138      * @param e
46139      */
46140     onMouseOver: function(e) {
46141         var me = this;
46142         if (!me.disabled && !e.within(me.el, true, true)) {
46143             me.onMouseEnter(e);
46144         }
46145     },
46146
46147     /**
46148      * @private
46149      * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
46150      * or the mouse leaves the encapsulating element.
46151      * The targets are interrogated to see what is being exited to where.
46152      * @param e
46153      */
46154     onMouseOut: function(e) {
46155         var me = this;
46156         if (!e.within(me.el, true, true)) {
46157             if (me.overMenuTrigger) {
46158                 me.onMenuTriggerOut(e);
46159             }
46160             me.onMouseLeave(e);
46161         }
46162     },
46163
46164     /**
46165      * @private
46166      * mousemove handler called when the mouse moves anywhere within the encapsulating element.
46167      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
46168      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
46169      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
46170      * events when needed. In the future we should consider making the trigger a separate element that
46171      * is absolutely positioned and sized over the trigger area.
46172      */
46173     onMouseMove: function(e) {
46174         var me = this,
46175             el = me.el,
46176             over = me.overMenuTrigger,
46177             overlap, btnSize;
46178
46179         if (me.split) {
46180             if (me.arrowAlign === 'right') {
46181                 overlap = e.getX() - el.getX();
46182                 btnSize = el.getWidth();
46183             } else {
46184                 overlap = e.getY() - el.getY();
46185                 btnSize = el.getHeight();
46186             }
46187
46188             if (overlap > (btnSize - me.getTriggerSize())) {
46189                 if (!over) {
46190                     me.onMenuTriggerOver(e);
46191                 }
46192             } else {
46193                 if (over) {
46194                     me.onMenuTriggerOut(e);
46195                 }
46196             }
46197         }
46198     },
46199
46200     /**
46201      * @private
46202      * Measures the size of the trigger area for menu and split buttons. Will be a width for
46203      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
46204      */
46205     getTriggerSize: function() {
46206         var me = this,
46207             size = me.triggerSize,
46208             side, sideFirstLetter, undef;
46209
46210         if (size === undef) {
46211             side = me.arrowAlign;
46212             sideFirstLetter = side.charAt(0);
46213             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
46214         }
46215         return size;
46216     },
46217
46218     /**
46219      * @private
46220      * virtual mouseenter handler called when it is detected that the mouseout event
46221      * signified the mouse entering the encapsulating element.
46222      * @param e
46223      */
46224     onMouseEnter: function(e) {
46225         var me = this;
46226         me.addClsWithUI(me.overCls);
46227         me.fireEvent('mouseover', me, e);
46228     },
46229
46230     /**
46231      * @private
46232      * virtual mouseleave handler called when it is detected that the mouseover event
46233      * signified the mouse entering the encapsulating element.
46234      * @param e
46235      */
46236     onMouseLeave: function(e) {
46237         var me = this;
46238         me.removeClsWithUI(me.overCls);
46239         me.fireEvent('mouseout', me, e);
46240     },
46241
46242     /**
46243      * @private
46244      * virtual mouseenter handler called when it is detected that the mouseover event
46245      * signified the mouse entering the arrow area of the button - the <em>.
46246      * @param e
46247      */
46248     onMenuTriggerOver: function(e) {
46249         var me = this;
46250         me.overMenuTrigger = true;
46251         me.fireEvent('menutriggerover', me, me.menu, e);
46252     },
46253
46254     /**
46255      * @private
46256      * virtual mouseleave handler called when it is detected that the mouseout event
46257      * signified the mouse leaving the arrow area of the button - the <em>.
46258      * @param e
46259      */
46260     onMenuTriggerOut: function(e) {
46261         var me = this;
46262         delete me.overMenuTrigger;
46263         me.fireEvent('menutriggerout', me, me.menu, e);
46264     },
46265
46266     // inherit docs
46267     enable : function(silent) {
46268         var me = this;
46269
46270         me.callParent(arguments);
46271
46272         me.removeClsWithUI('disabled');
46273
46274         return me;
46275     },
46276
46277     // inherit docs
46278     disable : function(silent) {
46279         var me = this;
46280
46281         me.callParent(arguments);
46282
46283         me.addClsWithUI('disabled');
46284         me.removeClsWithUI(me.overCls);
46285
46286         return me;
46287     },
46288
46289     /**
46290      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
46291      * @param {String} scale The scale to change to.
46292      */
46293     setScale: function(scale) {
46294         var me = this,
46295             ui = me.ui.replace('-' + me.scale, '');
46296
46297         //check if it is an allowed scale
46298         if (!Ext.Array.contains(me.allowedScales, scale)) {
46299             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
46300         }
46301
46302         me.scale = scale;
46303         me.setUI(ui);
46304     },
46305
46306     // inherit docs
46307     setUI: function(ui) {
46308         var me = this;
46309
46310         //we need to append the scale to the UI, if not already done
46311         if (me.scale && !ui.match(me.scale)) {
46312             ui = ui + '-' + me.scale;
46313         }
46314
46315         me.callParent([ui]);
46316
46317         // Set all the state classNames, as they need to include the UI
46318         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
46319     },
46320
46321     // private
46322     onFocus: function(e) {
46323         var me = this;
46324         if (!me.disabled) {
46325             me.addClsWithUI(me.focusCls);
46326         }
46327     },
46328
46329     // private
46330     onBlur: function(e) {
46331         var me = this;
46332         me.removeClsWithUI(me.focusCls);
46333     },
46334
46335     // private
46336     onMouseDown: function(e) {
46337         var me = this;
46338         if (!me.disabled && e.button === 0) {
46339             me.addClsWithUI(me.pressedCls);
46340             me.doc.on('mouseup', me.onMouseUp, me);
46341         }
46342     },
46343     // private
46344     onMouseUp: function(e) {
46345         var me = this;
46346         if (e.button === 0) {
46347             if (!me.pressed) {
46348                 me.removeClsWithUI(me.pressedCls);
46349             }
46350             me.doc.un('mouseup', me.onMouseUp, me);
46351         }
46352     },
46353     // private
46354     onMenuShow: function(e) {
46355         var me = this;
46356         me.ignoreNextClick = 0;
46357         me.addClsWithUI(me.menuActiveCls);
46358         me.fireEvent('menushow', me, me.menu);
46359     },
46360
46361     // private
46362     onMenuHide: function(e) {
46363         var me = this;
46364         me.removeClsWithUI(me.menuActiveCls);
46365         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
46366         me.fireEvent('menuhide', me, me.menu);
46367     },
46368
46369     // private
46370     restoreClick: function() {
46371         this.ignoreNextClick = 0;
46372     },
46373
46374     // private
46375     onDownKey: function() {
46376         var me = this;
46377
46378         if (!me.disabled) {
46379             if (me.menu) {
46380                 me.showMenu();
46381             }
46382         }
46383     },
46384
46385     /**
46386      * @private
46387      * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
46388      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
46389      * @return {Number[]} [top, right, bottom, left]
46390      */
46391     getPersistentBtnPadding: function() {
46392         var cls = Ext.button.Button,
46393             padding = cls.persistentPadding,
46394             btn, leftTop, btnEl, btnInnerEl;
46395
46396         if (!padding) {
46397             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
46398
46399             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
46400                 // Create auto-size button offscreen and measure its insides
46401                 btn = Ext.create('Ext.button.Button', {
46402                     renderTo: Ext.getBody(),
46403                     text: 'test',
46404                     style: 'position:absolute;top:-999px;'
46405                 });
46406                 btnEl = btn.btnEl;
46407                 btnInnerEl = btn.btnInnerEl;
46408                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
46409
46410                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
46411                 padding[0] = leftTop[1];
46412                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
46413                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
46414                 padding[3] = leftTop[0];
46415
46416                 btn.destroy();
46417             }
46418         }
46419
46420         return padding;
46421     }
46422
46423 }, function() {
46424     var groups = {};
46425
46426     function toggleGroup(btn, state) {
46427         var g, i, l;
46428         if (state) {
46429             g = groups[btn.toggleGroup];
46430             for (i = 0, l = g.length; i < l; i++) {
46431                 if (g[i] !== btn) {
46432                     g[i].toggle(false);
46433                 }
46434             }
46435         }
46436     }
46437
46438     /**
46439      * Private utility class used by Button
46440      * @hide
46441      */
46442     Ext.ButtonToggleManager = {
46443         register: function(btn) {
46444             if (!btn.toggleGroup) {
46445                 return;
46446             }
46447             var group = groups[btn.toggleGroup];
46448             if (!group) {
46449                 group = groups[btn.toggleGroup] = [];
46450             }
46451             group.push(btn);
46452             btn.on('toggle', toggleGroup);
46453         },
46454
46455         unregister: function(btn) {
46456             if (!btn.toggleGroup) {
46457                 return;
46458             }
46459             var group = groups[btn.toggleGroup];
46460             if (group) {
46461                 Ext.Array.remove(group, btn);
46462                 btn.un('toggle', toggleGroup);
46463             }
46464         },
46465
46466         /**
46467          * Gets the pressed button in the passed group or null
46468          * @param {String} group
46469          * @return {Ext.button.Button}
46470          */
46471         getPressed: function(group) {
46472             var g = groups[group],
46473                 i = 0,
46474                 len;
46475             if (g) {
46476                 for (len = g.length; i < len; i++) {
46477                     if (g[i].pressed === true) {
46478                         return g[i];
46479                     }
46480                 }
46481             }
46482             return null;
46483         }
46484     };
46485 });
46486
46487 /**
46488  * @class Ext.layout.container.boxOverflow.Menu
46489  * @extends Ext.layout.container.boxOverflow.None
46490  * @private
46491  */
46492 Ext.define('Ext.layout.container.boxOverflow.Menu', {
46493
46494     /* Begin Definitions */
46495
46496     extend: 'Ext.layout.container.boxOverflow.None',
46497     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
46498     alternateClassName: 'Ext.layout.boxOverflow.Menu',
46499     
46500     /* End Definitions */
46501
46502     /**
46503      * @cfg {String} afterCtCls
46504      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
46505      * which must always be present at the rightmost edge of the Container
46506      */
46507
46508     /**
46509      * @property noItemsMenuText
46510      * @type String
46511      * HTML fragment to render into the toolbar overflow menu if there are no items to display
46512      */
46513     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
46514
46515     constructor: function(layout) {
46516         var me = this;
46517
46518         me.callParent(arguments);
46519
46520         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
46521         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
46522
46523         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
46524         /**
46525          * @property menuItems
46526          * @type Array
46527          * Array of all items that are currently hidden and should go into the dropdown menu
46528          */
46529         me.menuItems = [];
46530     },
46531     
46532     onRemove: function(comp){
46533         Ext.Array.remove(this.menuItems, comp);
46534     },
46535
46536     handleOverflow: function(calculations, targetSize) {
46537         var me = this,
46538             layout = me.layout,
46539             methodName = 'get' + layout.parallelPrefixCap,
46540             newSize = {},
46541             posArgs = [null, null];
46542
46543         me.callParent(arguments);
46544         this.createMenu(calculations, targetSize);
46545         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
46546         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
46547
46548         // Center the menuTrigger button.
46549         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
46550         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
46551         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
46552
46553         return { targetSize: newSize };
46554     },
46555
46556     /**
46557      * @private
46558      * Called by the layout, when it determines that there is no overflow.
46559      * Also called as an interceptor to the layout's onLayout method to reshow
46560      * previously hidden overflowing items.
46561      */
46562     clearOverflow: function(calculations, targetSize) {
46563         var me = this,
46564             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
46565             items = me.menuItems,
46566             i = 0,
46567             length = items.length,
46568             item;
46569
46570         me.hideTrigger();
46571         for (; i < length; i++) {
46572             items[i].show();
46573         }
46574         items.length = 0;
46575
46576         return targetSize ? {
46577             targetSize: {
46578                 height: targetSize.height,
46579                 width : newWidth
46580             }
46581         } : null;
46582     },
46583
46584     /**
46585      * @private
46586      */
46587     showTrigger: function() {
46588         this.menuTrigger.show();
46589     },
46590
46591     /**
46592      * @private
46593      */
46594     hideTrigger: function() {
46595         if (this.menuTrigger !== undefined) {
46596             this.menuTrigger.hide();
46597         }
46598     },
46599
46600     /**
46601      * @private
46602      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
46603      */
46604     beforeMenuShow: function(menu) {
46605         var me = this,
46606             items = me.menuItems,
46607             i = 0,
46608             len   = items.length,
46609             item,
46610             prev;
46611
46612         var needsSep = function(group, prev){
46613             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
46614         };
46615
46616         me.clearMenu();
46617         menu.removeAll();
46618
46619         for (; i < len; i++) {
46620             item = items[i];
46621
46622             // Do not show a separator as a first item
46623             if (!i && (item instanceof Ext.toolbar.Separator)) {
46624                 continue;
46625             }
46626             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
46627                 menu.add('-');
46628             }
46629
46630             me.addComponentToMenu(menu, item);
46631             prev = item;
46632         }
46633
46634         // put something so the menu isn't empty if no compatible items found
46635         if (menu.items.length < 1) {
46636             menu.add(me.noItemsMenuText);
46637         }
46638     },
46639     
46640     /**
46641      * @private
46642      * Returns a menu config for a given component. This config is used to create a menu item
46643      * to be added to the expander menu
46644      * @param {Ext.Component} component The component to create the config for
46645      * @param {Boolean} hideOnClick Passed through to the menu item
46646      */
46647     createMenuConfig : function(component, hideOnClick) {
46648         var config = Ext.apply({}, component.initialConfig),
46649             group  = component.toggleGroup;
46650
46651         Ext.copyTo(config, component, [
46652             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
46653         ]);
46654
46655         Ext.apply(config, {
46656             text       : component.overflowText || component.text,
46657             hideOnClick: hideOnClick,
46658             destroyMenu: false
46659         });
46660
46661         if (group || component.enableToggle) {
46662             Ext.apply(config, {
46663                 group  : group,
46664                 checked: component.pressed,
46665                 listeners: {
46666                     checkchange: function(item, checked){
46667                         component.toggle(checked);
46668                     }
46669                 }
46670             });
46671         }
46672
46673         delete config.ownerCt;
46674         delete config.xtype;
46675         delete config.id;
46676         return config;
46677     },
46678
46679     /**
46680      * @private
46681      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
46682      * @param {Ext.menu.Menu} menu The menu to add to
46683      * @param {Ext.Component} component The component to add
46684      */
46685     addComponentToMenu : function(menu, component) {
46686         var me = this;
46687         if (component instanceof Ext.toolbar.Separator) {
46688             menu.add('-');
46689         } else if (component.isComponent) {
46690             if (component.isXType('splitbutton')) {
46691                 menu.add(me.createMenuConfig(component, true));
46692
46693             } else if (component.isXType('button')) {
46694                 menu.add(me.createMenuConfig(component, !component.menu));
46695
46696             } else if (component.isXType('buttongroup')) {
46697                 component.items.each(function(item){
46698                      me.addComponentToMenu(menu, item);
46699                 });
46700             } else {
46701                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
46702             }
46703         }
46704     },
46705
46706     /**
46707      * @private
46708      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
46709      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
46710      */
46711     clearMenu : function() {
46712         var menu = this.moreMenu;
46713         if (menu && menu.items) {
46714             menu.items.each(function(item) {
46715                 if (item.menu) {
46716                     delete item.menu;
46717                 }
46718             });
46719         }
46720     },
46721
46722     /**
46723      * @private
46724      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
46725      * in the layout are too wide to fit in the space available
46726      */
46727     createMenu: function(calculations, targetSize) {
46728         var me = this,
46729             layout = me.layout,
46730             startProp = layout.parallelBefore,
46731             sizeProp = layout.parallelPrefix,
46732             available = targetSize[sizeProp],
46733             boxes = calculations.boxes,
46734             i = 0,
46735             len = boxes.length,
46736             box;
46737
46738         if (!me.menuTrigger) {
46739             me.createInnerElements();
46740
46741             /**
46742              * @private
46743              * @property menu
46744              * @type Ext.menu.Menu
46745              * The expand menu - holds items for every item that cannot be shown
46746              * because the container is currently not large enough.
46747              */
46748             me.menu = Ext.create('Ext.menu.Menu', {
46749                 listeners: {
46750                     scope: me,
46751                     beforeshow: me.beforeMenuShow
46752                 }
46753             });
46754
46755             /**
46756              * @private
46757              * @property menuTrigger
46758              * @type Ext.button.Button
46759              * The expand button which triggers the overflow menu to be shown
46760              */
46761             me.menuTrigger = Ext.create('Ext.button.Button', {
46762                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
46763                 iconCls : me.layout.owner.menuTriggerCls,
46764                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
46765                 menu    : me.menu,
46766                 getSplitCls: function() { return '';},
46767                 renderTo: me.afterCt
46768             });
46769         }
46770         me.showTrigger();
46771         available -= me.afterCt.getWidth();
46772
46773         // Hide all items which are off the end, and store them to allow them to be restored
46774         // before each layout operation.
46775         me.menuItems.length = 0;
46776         for (; i < len; i++) {
46777             box = boxes[i];
46778             if (box[startProp] + box[sizeProp] > available) {
46779                 me.menuItems.push(box.component);
46780                 box.component.hide();
46781             }
46782         }
46783     },
46784
46785     /**
46786      * @private
46787      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
46788      * @param {Ext.container.Container} container The Container attached to this Layout instance
46789      * @param {Ext.Element} target The target Element
46790      */
46791     createInnerElements: function() {
46792         var me = this,
46793             target = me.layout.getRenderTarget();
46794
46795         if (!this.afterCt) {
46796             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
46797             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
46798         }
46799     },
46800
46801     /**
46802      * @private
46803      */
46804     destroy: function() {
46805         Ext.destroy(this.menu, this.menuTrigger);
46806     }
46807 });
46808 /**
46809  * This class represents a rectangular region in X,Y space, and performs geometric
46810  * transformations or tests upon the region.
46811  *
46812  * This class may be used to compare the document regions occupied by elements.
46813  */
46814 Ext.define('Ext.util.Region', {
46815
46816     /* Begin Definitions */
46817
46818     requires: ['Ext.util.Offset'],
46819
46820     statics: {
46821         /**
46822          * @static
46823          * Retrieves an Ext.util.Region for a particular element.
46824          * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
46825          * @returns {Ext.util.Region} region
46826          */
46827         getRegion: function(el) {
46828             return Ext.fly(el).getPageBox(true);
46829         },
46830
46831         /**
46832          * @static
46833          * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
46834          * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
46835          * @return {Ext.util.Region} region The Region constructed based on the passed object
46836          */
46837         from: function(o) {
46838             return new this(o.top, o.right, o.bottom, o.left);
46839         }
46840     },
46841
46842     /* End Definitions */
46843
46844     /**
46845      * Creates a region from the bounding sides.
46846      * @param {Number} top Top The topmost pixel of the Region.
46847      * @param {Number} right Right The rightmost pixel of the Region.
46848      * @param {Number} bottom Bottom The bottom pixel of the Region.
46849      * @param {Number} left Left The leftmost pixel of the Region.
46850      */
46851     constructor : function(t, r, b, l) {
46852         var me = this;
46853         me.y = me.top = me[1] = t;
46854         me.right = r;
46855         me.bottom = b;
46856         me.x = me.left = me[0] = l;
46857     },
46858
46859     /**
46860      * Checks if this region completely contains the region that is passed in.
46861      * @param {Ext.util.Region} region
46862      * @return {Boolean}
46863      */
46864     contains : function(region) {
46865         var me = this;
46866         return (region.x >= me.x &&
46867                 region.right <= me.right &&
46868                 region.y >= me.y &&
46869                 region.bottom <= me.bottom);
46870
46871     },
46872
46873     /**
46874      * Checks if this region intersects the region passed in.
46875      * @param {Ext.util.Region} region
46876      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
46877      */
46878     intersect : function(region) {
46879         var me = this,
46880             t = Math.max(me.y, region.y),
46881             r = Math.min(me.right, region.right),
46882             b = Math.min(me.bottom, region.bottom),
46883             l = Math.max(me.x, region.x);
46884
46885         if (b > t && r > l) {
46886             return new this.self(t, r, b, l);
46887         }
46888         else {
46889             return false;
46890         }
46891     },
46892
46893     /**
46894      * Returns the smallest region that contains the current AND targetRegion.
46895      * @param {Ext.util.Region} region
46896      * @return {Ext.util.Region} a new region
46897      */
46898     union : function(region) {
46899         var me = this,
46900             t = Math.min(me.y, region.y),
46901             r = Math.max(me.right, region.right),
46902             b = Math.max(me.bottom, region.bottom),
46903             l = Math.min(me.x, region.x);
46904
46905         return new this.self(t, r, b, l);
46906     },
46907
46908     /**
46909      * Modifies the current region to be constrained to the targetRegion.
46910      * @param {Ext.util.Region} targetRegion
46911      * @return {Ext.util.Region} this
46912      */
46913     constrainTo : function(r) {
46914         var me = this,
46915             constrain = Ext.Number.constrain;
46916         me.top = me.y = constrain(me.top, r.y, r.bottom);
46917         me.bottom = constrain(me.bottom, r.y, r.bottom);
46918         me.left = me.x = constrain(me.left, r.x, r.right);
46919         me.right = constrain(me.right, r.x, r.right);
46920         return me;
46921     },
46922
46923     /**
46924      * Modifies the current region to be adjusted by offsets.
46925      * @param {Number} top top offset
46926      * @param {Number} right right offset
46927      * @param {Number} bottom bottom offset
46928      * @param {Number} left left offset
46929      * @return {Ext.util.Region} this
46930      */
46931     adjust : function(t, r, b, l) {
46932         var me = this;
46933         me.top = me.y += t;
46934         me.left = me.x += l;
46935         me.right += r;
46936         me.bottom += b;
46937         return me;
46938     },
46939
46940     /**
46941      * Get the offset amount of a point outside the region
46942      * @param {String} [axis]
46943      * @param {Ext.util.Point} [p] the point
46944      * @return {Ext.util.Offset}
46945      */
46946     getOutOfBoundOffset: function(axis, p) {
46947         if (!Ext.isObject(axis)) {
46948             if (axis == 'x') {
46949                 return this.getOutOfBoundOffsetX(p);
46950             } else {
46951                 return this.getOutOfBoundOffsetY(p);
46952             }
46953         } else {
46954             p = axis;
46955             var d = Ext.create('Ext.util.Offset');
46956             d.x = this.getOutOfBoundOffsetX(p.x);
46957             d.y = this.getOutOfBoundOffsetY(p.y);
46958             return d;
46959         }
46960
46961     },
46962
46963     /**
46964      * Get the offset amount on the x-axis
46965      * @param {Number} p the offset
46966      * @return {Number}
46967      */
46968     getOutOfBoundOffsetX: function(p) {
46969         if (p <= this.x) {
46970             return this.x - p;
46971         } else if (p >= this.right) {
46972             return this.right - p;
46973         }
46974
46975         return 0;
46976     },
46977
46978     /**
46979      * Get the offset amount on the y-axis
46980      * @param {Number} p the offset
46981      * @return {Number}
46982      */
46983     getOutOfBoundOffsetY: function(p) {
46984         if (p <= this.y) {
46985             return this.y - p;
46986         } else if (p >= this.bottom) {
46987             return this.bottom - p;
46988         }
46989
46990         return 0;
46991     },
46992
46993     /**
46994      * Check whether the point / offset is out of bound
46995      * @param {String} [axis]
46996      * @param {Ext.util.Point/Number} [p] the point / offset
46997      * @return {Boolean}
46998      */
46999     isOutOfBound: function(axis, p) {
47000         if (!Ext.isObject(axis)) {
47001             if (axis == 'x') {
47002                 return this.isOutOfBoundX(p);
47003             } else {
47004                 return this.isOutOfBoundY(p);
47005             }
47006         } else {
47007             p = axis;
47008             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
47009         }
47010     },
47011
47012     /**
47013      * Check whether the offset is out of bound in the x-axis
47014      * @param {Number} p the offset
47015      * @return {Boolean}
47016      */
47017     isOutOfBoundX: function(p) {
47018         return (p < this.x || p > this.right);
47019     },
47020
47021     /**
47022      * Check whether the offset is out of bound in the y-axis
47023      * @param {Number} p the offset
47024      * @return {Boolean}
47025      */
47026     isOutOfBoundY: function(p) {
47027         return (p < this.y || p > this.bottom);
47028     },
47029
47030     /**
47031      * Restrict a point within the region by a certain factor.
47032      * @param {String} [axis]
47033      * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
47034      * @param {Number} [factor]
47035      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
47036      * @private
47037      */
47038     restrict: function(axis, p, factor) {
47039         if (Ext.isObject(axis)) {
47040             var newP;
47041
47042             factor = p;
47043             p = axis;
47044
47045             if (p.copy) {
47046                 newP = p.copy();
47047             }
47048             else {
47049                 newP = {
47050                     x: p.x,
47051                     y: p.y
47052                 };
47053             }
47054
47055             newP.x = this.restrictX(p.x, factor);
47056             newP.y = this.restrictY(p.y, factor);
47057             return newP;
47058         } else {
47059             if (axis == 'x') {
47060                 return this.restrictX(p, factor);
47061             } else {
47062                 return this.restrictY(p, factor);
47063             }
47064         }
47065     },
47066
47067     /**
47068      * Restrict an offset within the region by a certain factor, on the x-axis
47069      * @param {Number} p
47070      * @param {Number} [factor=1] The factor.
47071      * @return {Number}
47072      * @private
47073      */
47074     restrictX : function(p, factor) {
47075         if (!factor) {
47076             factor = 1;
47077         }
47078
47079         if (p <= this.x) {
47080             p -= (p - this.x) * factor;
47081         }
47082         else if (p >= this.right) {
47083             p -= (p - this.right) * factor;
47084         }
47085         return p;
47086     },
47087
47088     /**
47089      * Restrict an offset within the region by a certain factor, on the y-axis
47090      * @param {Number} p
47091      * @param {Number} [factor] The factor, defaults to 1
47092      * @return {Number}
47093      * @private
47094      */
47095     restrictY : function(p, factor) {
47096         if (!factor) {
47097             factor = 1;
47098         }
47099
47100         if (p <= this.y) {
47101             p -= (p - this.y) * factor;
47102         }
47103         else if (p >= this.bottom) {
47104             p -= (p - this.bottom) * factor;
47105         }
47106         return p;
47107     },
47108
47109     /**
47110      * Get the width / height of this region
47111      * @return {Object} an object with width and height properties
47112      * @private
47113      */
47114     getSize: function() {
47115         return {
47116             width: this.right - this.x,
47117             height: this.bottom - this.y
47118         };
47119     },
47120
47121     /**
47122      * Create a copy of this Region.
47123      * @return {Ext.util.Region}
47124      */
47125     copy: function() {
47126         return new this.self(this.y, this.right, this.bottom, this.x);
47127     },
47128
47129     /**
47130      * Copy the values of another Region to this Region
47131      * @param {Ext.util.Region} p The region to copy from.
47132      * @return {Ext.util.Region} This Region
47133      */
47134     copyFrom: function(p) {
47135         var me = this;
47136         me.top = me.y = me[1] = p.y;
47137         me.right = p.right;
47138         me.bottom = p.bottom;
47139         me.left = me.x = me[0] = p.x;
47140
47141         return this;
47142     },
47143
47144     /*
47145      * Dump this to an eye-friendly string, great for debugging
47146      * @return {String}
47147      */
47148     toString: function() {
47149         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
47150     },
47151
47152     /**
47153      * Translate this region by the given offset amount
47154      * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
47155      * Or the x value is using the two argument form.
47156      * @param {Number} y The y value unless using an Offset object.
47157      * @return {Ext.util.Region} this This Region
47158      */
47159     translateBy: function(x, y) {
47160         if (arguments.length == 1) {
47161             y = x.y;
47162             x = x.x;
47163         }
47164         var me = this;
47165         me.top = me.y += y;
47166         me.right += x;
47167         me.bottom += y;
47168         me.left = me.x += x;
47169
47170         return me;
47171     },
47172
47173     /**
47174      * Round all the properties of this region
47175      * @return {Ext.util.Region} this This Region
47176      */
47177     round: function() {
47178         var me = this;
47179         me.top = me.y = Math.round(me.y);
47180         me.right = Math.round(me.right);
47181         me.bottom = Math.round(me.bottom);
47182         me.left = me.x = Math.round(me.x);
47183
47184         return me;
47185     },
47186
47187     /**
47188      * Check whether this region is equivalent to the given region
47189      * @param {Ext.util.Region} region The region to compare with
47190      * @return {Boolean}
47191      */
47192     equals: function(region) {
47193         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
47194     }
47195 });
47196
47197 /*
47198  * This is a derivative of the similarly named class in the YUI Library.
47199  * The original license:
47200  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
47201  * Code licensed under the BSD License:
47202  * http://developer.yahoo.net/yui/license.txt
47203  */
47204
47205
47206 /**
47207  * @class Ext.dd.DragDropManager
47208  * DragDropManager is a singleton that tracks the element interaction for
47209  * all DragDrop items in the window.  Generally, you will not call
47210  * this class directly, but it does have helper methods that could
47211  * be useful in your DragDrop implementations.
47212  * @singleton
47213  */
47214 Ext.define('Ext.dd.DragDropManager', {
47215     singleton: true,
47216
47217     requires: ['Ext.util.Region'],
47218
47219     uses: ['Ext.tip.QuickTipManager'],
47220
47221     // shorter ClassName, to save bytes and use internally
47222     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
47223
47224     /**
47225      * Two dimensional Array of registered DragDrop objects.  The first
47226      * dimension is the DragDrop item group, the second the DragDrop
47227      * object.
47228      * @property ids
47229      * @type String[]
47230      * @private
47231      */
47232     ids: {},
47233
47234     /**
47235      * Array of element ids defined as drag handles.  Used to determine
47236      * if the element that generated the mousedown event is actually the
47237      * handle and not the html element itself.
47238      * @property handleIds
47239      * @type String[]
47240      * @private
47241      */
47242     handleIds: {},
47243
47244     /**
47245      * the DragDrop object that is currently being dragged
47246      * @property {Ext.dd.DragDrop} dragCurrent
47247      * @private
47248      **/
47249     dragCurrent: null,
47250
47251     /**
47252      * the DragDrop object(s) that are being hovered over
47253      * @property {Ext.dd.DragDrop[]} dragOvers
47254      * @private
47255      */
47256     dragOvers: {},
47257
47258     /**
47259      * the X distance between the cursor and the object being dragged
47260      * @property deltaX
47261      * @type Number
47262      * @private
47263      */
47264     deltaX: 0,
47265
47266     /**
47267      * the Y distance between the cursor and the object being dragged
47268      * @property deltaY
47269      * @type Number
47270      * @private
47271      */
47272     deltaY: 0,
47273
47274     /**
47275      * Flag to determine if we should prevent the default behavior of the
47276      * events we define. By default this is true, but this can be set to
47277      * false if you need the default behavior (not recommended)
47278      * @property preventDefault
47279      * @type Boolean
47280      */
47281     preventDefault: true,
47282
47283     /**
47284      * Flag to determine if we should stop the propagation of the events
47285      * we generate. This is true by default but you may want to set it to
47286      * false if the html element contains other features that require the
47287      * mouse click.
47288      * @property stopPropagation
47289      * @type Boolean
47290      */
47291     stopPropagation: true,
47292
47293     /**
47294      * Internal flag that is set to true when drag and drop has been
47295      * intialized
47296      * @property initialized
47297      * @private
47298      */
47299     initialized: false,
47300
47301     /**
47302      * All drag and drop can be disabled.
47303      * @property locked
47304      * @private
47305      */
47306     locked: false,
47307
47308     /**
47309      * Called the first time an element is registered.
47310      * @method init
47311      * @private
47312      */
47313     init: function() {
47314         this.initialized = true;
47315     },
47316
47317     /**
47318      * In point mode, drag and drop interaction is defined by the
47319      * location of the cursor during the drag/drop
47320      * @property POINT
47321      * @type Number
47322      */
47323     POINT: 0,
47324
47325     /**
47326      * In intersect mode, drag and drop interaction is defined by the
47327      * overlap of two or more drag and drop objects.
47328      * @property INTERSECT
47329      * @type Number
47330      */
47331     INTERSECT: 1,
47332
47333     /**
47334      * The current drag and drop mode.  Default: POINT
47335      * @property mode
47336      * @type Number
47337      */
47338     mode: 0,
47339
47340     /**
47341      * Runs method on all drag and drop objects
47342      * @method _execOnAll
47343      * @private
47344      */
47345     _execOnAll: function(sMethod, args) {
47346         for (var i in this.ids) {
47347             for (var j in this.ids[i]) {
47348                 var oDD = this.ids[i][j];
47349                 if (! this.isTypeOfDD(oDD)) {
47350                     continue;
47351                 }
47352                 oDD[sMethod].apply(oDD, args);
47353             }
47354         }
47355     },
47356
47357     /**
47358      * Drag and drop initialization.  Sets up the global event handlers
47359      * @method _onLoad
47360      * @private
47361      */
47362     _onLoad: function() {
47363
47364         this.init();
47365
47366         var Event = Ext.EventManager;
47367         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
47368         Event.on(document, "mousemove", this.handleMouseMove, this, true);
47369         Event.on(window,   "unload",    this._onUnload, this, true);
47370         Event.on(window,   "resize",    this._onResize, this, true);
47371         // Event.on(window,   "mouseout",    this._test);
47372
47373     },
47374
47375     /**
47376      * Reset constraints on all drag and drop objs
47377      * @method _onResize
47378      * @private
47379      */
47380     _onResize: function(e) {
47381         this._execOnAll("resetConstraints", []);
47382     },
47383
47384     /**
47385      * Lock all drag and drop functionality
47386      * @method lock
47387      */
47388     lock: function() { this.locked = true; },
47389
47390     /**
47391      * Unlock all drag and drop functionality
47392      * @method unlock
47393      */
47394     unlock: function() { this.locked = false; },
47395
47396     /**
47397      * Is drag and drop locked?
47398      * @method isLocked
47399      * @return {Boolean} True if drag and drop is locked, false otherwise.
47400      */
47401     isLocked: function() { return this.locked; },
47402
47403     /**
47404      * Location cache that is set for all drag drop objects when a drag is
47405      * initiated, cleared when the drag is finished.
47406      * @property locationCache
47407      * @private
47408      */
47409     locationCache: {},
47410
47411     /**
47412      * Set useCache to false if you want to force object the lookup of each
47413      * drag and drop linked element constantly during a drag.
47414      * @property useCache
47415      * @type Boolean
47416      */
47417     useCache: true,
47418
47419     /**
47420      * The number of pixels that the mouse needs to move after the
47421      * mousedown before the drag is initiated.  Default=3;
47422      * @property clickPixelThresh
47423      * @type Number
47424      */
47425     clickPixelThresh: 3,
47426
47427     /**
47428      * The number of milliseconds after the mousedown event to initiate the
47429      * drag if we don't get a mouseup event. Default=350
47430      * @property clickTimeThresh
47431      * @type Number
47432      */
47433     clickTimeThresh: 350,
47434
47435     /**
47436      * Flag that indicates that either the drag pixel threshold or the
47437      * mousdown time threshold has been met
47438      * @property dragThreshMet
47439      * @type Boolean
47440      * @private
47441      */
47442     dragThreshMet: false,
47443
47444     /**
47445      * Timeout used for the click time threshold
47446      * @property clickTimeout
47447      * @type Object
47448      * @private
47449      */
47450     clickTimeout: null,
47451
47452     /**
47453      * The X position of the mousedown event stored for later use when a
47454      * drag threshold is met.
47455      * @property startX
47456      * @type Number
47457      * @private
47458      */
47459     startX: 0,
47460
47461     /**
47462      * The Y position of the mousedown event stored for later use when a
47463      * drag threshold is met.
47464      * @property startY
47465      * @type Number
47466      * @private
47467      */
47468     startY: 0,
47469
47470     /**
47471      * Each DragDrop instance must be registered with the DragDropManager.
47472      * This is executed in DragDrop.init()
47473      * @method regDragDrop
47474      * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
47475      * @param {String} sGroup the name of the group this element belongs to
47476      */
47477     regDragDrop: function(oDD, sGroup) {
47478         if (!this.initialized) { this.init(); }
47479
47480         if (!this.ids[sGroup]) {
47481             this.ids[sGroup] = {};
47482         }
47483         this.ids[sGroup][oDD.id] = oDD;
47484     },
47485
47486     /**
47487      * Removes the supplied dd instance from the supplied group. Executed
47488      * by DragDrop.removeFromGroup, so don't call this function directly.
47489      * @method removeDDFromGroup
47490      * @private
47491      */
47492     removeDDFromGroup: function(oDD, sGroup) {
47493         if (!this.ids[sGroup]) {
47494             this.ids[sGroup] = {};
47495         }
47496
47497         var obj = this.ids[sGroup];
47498         if (obj && obj[oDD.id]) {
47499             delete obj[oDD.id];
47500         }
47501     },
47502
47503     /**
47504      * Unregisters a drag and drop item.  This is executed in
47505      * DragDrop.unreg, use that method instead of calling this directly.
47506      * @method _remove
47507      * @private
47508      */
47509     _remove: function(oDD) {
47510         for (var g in oDD.groups) {
47511             if (g && this.ids[g] && this.ids[g][oDD.id]) {
47512                 delete this.ids[g][oDD.id];
47513             }
47514         }
47515         delete this.handleIds[oDD.id];
47516     },
47517
47518     /**
47519      * Each DragDrop handle element must be registered.  This is done
47520      * automatically when executing DragDrop.setHandleElId()
47521      * @method regHandle
47522      * @param {String} sDDId the DragDrop id this element is a handle for
47523      * @param {String} sHandleId the id of the element that is the drag
47524      * handle
47525      */
47526     regHandle: function(sDDId, sHandleId) {
47527         if (!this.handleIds[sDDId]) {
47528             this.handleIds[sDDId] = {};
47529         }
47530         this.handleIds[sDDId][sHandleId] = sHandleId;
47531     },
47532
47533     /**
47534      * Utility function to determine if a given element has been
47535      * registered as a drag drop item.
47536      * @method isDragDrop
47537      * @param {String} id the element id to check
47538      * @return {Boolean} true if this element is a DragDrop item,
47539      * false otherwise
47540      */
47541     isDragDrop: function(id) {
47542         return ( this.getDDById(id) ) ? true : false;
47543     },
47544
47545     /**
47546      * Returns the drag and drop instances that are in all groups the
47547      * passed in instance belongs to.
47548      * @method getRelated
47549      * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
47550      * @param {Boolean} bTargetsOnly if true, only return targetable objs
47551      * @return {Ext.dd.DragDrop[]} the related instances
47552      */
47553     getRelated: function(p_oDD, bTargetsOnly) {
47554         var oDDs = [];
47555         for (var i in p_oDD.groups) {
47556             for (var j in this.ids[i]) {
47557                 var dd = this.ids[i][j];
47558                 if (! this.isTypeOfDD(dd)) {
47559                     continue;
47560                 }
47561                 if (!bTargetsOnly || dd.isTarget) {
47562                     oDDs[oDDs.length] = dd;
47563                 }
47564             }
47565         }
47566
47567         return oDDs;
47568     },
47569
47570     /**
47571      * Returns true if the specified dd target is a legal target for
47572      * the specifice drag obj
47573      * @method isLegalTarget
47574      * @param {Ext.dd.DragDrop} oDD the drag obj
47575      * @param {Ext.dd.DragDrop} oTargetDD the target
47576      * @return {Boolean} true if the target is a legal target for the
47577      * dd obj
47578      */
47579     isLegalTarget: function (oDD, oTargetDD) {
47580         var targets = this.getRelated(oDD, true);
47581         for (var i=0, len=targets.length;i<len;++i) {
47582             if (targets[i].id == oTargetDD.id) {
47583                 return true;
47584             }
47585         }
47586
47587         return false;
47588     },
47589
47590     /**
47591      * My goal is to be able to transparently determine if an object is
47592      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
47593      * returns "object", oDD.constructor.toString() always returns
47594      * "DragDrop" and not the name of the subclass.  So for now it just
47595      * evaluates a well-known variable in DragDrop.
47596      * @method isTypeOfDD
47597      * @param {Object} the object to evaluate
47598      * @return {Boolean} true if typeof oDD = DragDrop
47599      */
47600     isTypeOfDD: function (oDD) {
47601         return (oDD && oDD.__ygDragDrop);
47602     },
47603
47604     /**
47605      * Utility function to determine if a given element has been
47606      * registered as a drag drop handle for the given Drag Drop object.
47607      * @method isHandle
47608      * @param {String} id the element id to check
47609      * @return {Boolean} true if this element is a DragDrop handle, false
47610      * otherwise
47611      */
47612     isHandle: function(sDDId, sHandleId) {
47613         return ( this.handleIds[sDDId] &&
47614                         this.handleIds[sDDId][sHandleId] );
47615     },
47616
47617     /**
47618      * Returns the DragDrop instance for a given id
47619      * @method getDDById
47620      * @param {String} id the id of the DragDrop object
47621      * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
47622      */
47623     getDDById: function(id) {
47624         for (var i in this.ids) {
47625             if (this.ids[i][id]) {
47626                 return this.ids[i][id];
47627             }
47628         }
47629         return null;
47630     },
47631
47632     /**
47633      * Fired after a registered DragDrop object gets the mousedown event.
47634      * Sets up the events required to track the object being dragged
47635      * @method handleMouseDown
47636      * @param {Event} e the event
47637      * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
47638      * @private
47639      */
47640     handleMouseDown: function(e, oDD) {
47641         if(Ext.tip.QuickTipManager){
47642             Ext.tip.QuickTipManager.ddDisable();
47643         }
47644         if(this.dragCurrent){
47645             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
47646             // so clean up first to avoid breaking the next drag
47647             this.handleMouseUp(e);
47648         }
47649
47650         this.currentTarget = e.getTarget();
47651         this.dragCurrent = oDD;
47652
47653         var el = oDD.getEl();
47654
47655         // track start position
47656         this.startX = e.getPageX();
47657         this.startY = e.getPageY();
47658
47659         this.deltaX = this.startX - el.offsetLeft;
47660         this.deltaY = this.startY - el.offsetTop;
47661
47662         this.dragThreshMet = false;
47663
47664         this.clickTimeout = setTimeout(
47665                 function() {
47666                     var DDM = Ext.dd.DragDropManager;
47667                     DDM.startDrag(DDM.startX, DDM.startY);
47668                 },
47669                 this.clickTimeThresh );
47670     },
47671
47672     /**
47673      * Fired when either the drag pixel threshol or the mousedown hold
47674      * time threshold has been met.
47675      * @method startDrag
47676      * @param {Number} x the X position of the original mousedown
47677      * @param {Number} y the Y position of the original mousedown
47678      */
47679     startDrag: function(x, y) {
47680         clearTimeout(this.clickTimeout);
47681         if (this.dragCurrent) {
47682             this.dragCurrent.b4StartDrag(x, y);
47683             this.dragCurrent.startDrag(x, y);
47684         }
47685         this.dragThreshMet = true;
47686     },
47687
47688     /**
47689      * Internal function to handle the mouseup event.  Will be invoked
47690      * from the context of the document.
47691      * @method handleMouseUp
47692      * @param {Event} e the event
47693      * @private
47694      */
47695     handleMouseUp: function(e) {
47696
47697         if(Ext.tip && Ext.tip.QuickTipManager){
47698             Ext.tip.QuickTipManager.ddEnable();
47699         }
47700         if (! this.dragCurrent) {
47701             return;
47702         }
47703
47704         clearTimeout(this.clickTimeout);
47705
47706         if (this.dragThreshMet) {
47707             this.fireEvents(e, true);
47708         } else {
47709         }
47710
47711         this.stopDrag(e);
47712
47713         this.stopEvent(e);
47714     },
47715
47716     /**
47717      * Utility to stop event propagation and event default, if these
47718      * features are turned on.
47719      * @method stopEvent
47720      * @param {Event} e the event as returned by this.getEvent()
47721      */
47722     stopEvent: function(e){
47723         if(this.stopPropagation) {
47724             e.stopPropagation();
47725         }
47726
47727         if (this.preventDefault) {
47728             e.preventDefault();
47729         }
47730     },
47731
47732     /**
47733      * Internal function to clean up event handlers after the drag
47734      * operation is complete
47735      * @method stopDrag
47736      * @param {Event} e the event
47737      * @private
47738      */
47739     stopDrag: function(e) {
47740         // Fire the drag end event for the item that was dragged
47741         if (this.dragCurrent) {
47742             if (this.dragThreshMet) {
47743                 this.dragCurrent.b4EndDrag(e);
47744                 this.dragCurrent.endDrag(e);
47745             }
47746
47747             this.dragCurrent.onMouseUp(e);
47748         }
47749
47750         this.dragCurrent = null;
47751         this.dragOvers = {};
47752     },
47753
47754     /**
47755      * Internal function to handle the mousemove event.  Will be invoked
47756      * from the context of the html element.
47757      *
47758      * @TODO figure out what we can do about mouse events lost when the
47759      * user drags objects beyond the window boundary.  Currently we can
47760      * detect this in internet explorer by verifying that the mouse is
47761      * down during the mousemove event.  Firefox doesn't give us the
47762      * button state on the mousemove event.
47763      * @method handleMouseMove
47764      * @param {Event} e the event
47765      * @private
47766      */
47767     handleMouseMove: function(e) {
47768         if (! this.dragCurrent) {
47769             return true;
47770         }
47771         // var button = e.which || e.button;
47772
47773         // check for IE mouseup outside of page boundary
47774         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
47775             this.stopEvent(e);
47776             return this.handleMouseUp(e);
47777         }
47778
47779         if (!this.dragThreshMet) {
47780             var diffX = Math.abs(this.startX - e.getPageX());
47781             var diffY = Math.abs(this.startY - e.getPageY());
47782             if (diffX > this.clickPixelThresh ||
47783                         diffY > this.clickPixelThresh) {
47784                 this.startDrag(this.startX, this.startY);
47785             }
47786         }
47787
47788         if (this.dragThreshMet) {
47789             this.dragCurrent.b4Drag(e);
47790             this.dragCurrent.onDrag(e);
47791             if(!this.dragCurrent.moveOnly){
47792                 this.fireEvents(e, false);
47793             }
47794         }
47795
47796         this.stopEvent(e);
47797
47798         return true;
47799     },
47800
47801     /**
47802      * Iterates over all of the DragDrop elements to find ones we are
47803      * hovering over or dropping on
47804      * @method fireEvents
47805      * @param {Event} e the event
47806      * @param {Boolean} isDrop is this a drop op or a mouseover op?
47807      * @private
47808      */
47809     fireEvents: function(e, isDrop) {
47810         var dc = this.dragCurrent;
47811
47812         // If the user did the mouse up outside of the window, we could
47813         // get here even though we have ended the drag.
47814         if (!dc || dc.isLocked()) {
47815             return;
47816         }
47817
47818         var pt = e.getPoint();
47819
47820         // cache the previous dragOver array
47821         var oldOvers = [];
47822
47823         var outEvts   = [];
47824         var overEvts  = [];
47825         var dropEvts  = [];
47826         var enterEvts = [];
47827
47828         // Check to see if the object(s) we were hovering over is no longer
47829         // being hovered over so we can fire the onDragOut event
47830         for (var i in this.dragOvers) {
47831
47832             var ddo = this.dragOvers[i];
47833
47834             if (! this.isTypeOfDD(ddo)) {
47835                 continue;
47836             }
47837
47838             if (! this.isOverTarget(pt, ddo, this.mode)) {
47839                 outEvts.push( ddo );
47840             }
47841
47842             oldOvers[i] = true;
47843             delete this.dragOvers[i];
47844         }
47845
47846         for (var sGroup in dc.groups) {
47847
47848             if ("string" != typeof sGroup) {
47849                 continue;
47850             }
47851
47852             for (i in this.ids[sGroup]) {
47853                 var oDD = this.ids[sGroup][i];
47854                 if (! this.isTypeOfDD(oDD)) {
47855                     continue;
47856                 }
47857
47858                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
47859                     if (this.isOverTarget(pt, oDD, this.mode)) {
47860                         // look for drop interactions
47861                         if (isDrop) {
47862                             dropEvts.push( oDD );
47863                         // look for drag enter and drag over interactions
47864                         } else {
47865
47866                             // initial drag over: dragEnter fires
47867                             if (!oldOvers[oDD.id]) {
47868                                 enterEvts.push( oDD );
47869                             // subsequent drag overs: dragOver fires
47870                             } else {
47871                                 overEvts.push( oDD );
47872                             }
47873
47874                             this.dragOvers[oDD.id] = oDD;
47875                         }
47876                     }
47877                 }
47878             }
47879         }
47880
47881         if (this.mode) {
47882             if (outEvts.length) {
47883                 dc.b4DragOut(e, outEvts);
47884                 dc.onDragOut(e, outEvts);
47885             }
47886
47887             if (enterEvts.length) {
47888                 dc.onDragEnter(e, enterEvts);
47889             }
47890
47891             if (overEvts.length) {
47892                 dc.b4DragOver(e, overEvts);
47893                 dc.onDragOver(e, overEvts);
47894             }
47895
47896             if (dropEvts.length) {
47897                 dc.b4DragDrop(e, dropEvts);
47898                 dc.onDragDrop(e, dropEvts);
47899             }
47900
47901         } else {
47902             // fire dragout events
47903             var len = 0;
47904             for (i=0, len=outEvts.length; i<len; ++i) {
47905                 dc.b4DragOut(e, outEvts[i].id);
47906                 dc.onDragOut(e, outEvts[i].id);
47907             }
47908
47909             // fire enter events
47910             for (i=0,len=enterEvts.length; i<len; ++i) {
47911                 // dc.b4DragEnter(e, oDD.id);
47912                 dc.onDragEnter(e, enterEvts[i].id);
47913             }
47914
47915             // fire over events
47916             for (i=0,len=overEvts.length; i<len; ++i) {
47917                 dc.b4DragOver(e, overEvts[i].id);
47918                 dc.onDragOver(e, overEvts[i].id);
47919             }
47920
47921             // fire drop events
47922             for (i=0, len=dropEvts.length; i<len; ++i) {
47923                 dc.b4DragDrop(e, dropEvts[i].id);
47924                 dc.onDragDrop(e, dropEvts[i].id);
47925             }
47926
47927         }
47928
47929         // notify about a drop that did not find a target
47930         if (isDrop && !dropEvts.length) {
47931             dc.onInvalidDrop(e);
47932         }
47933
47934     },
47935
47936     /**
47937      * Helper function for getting the best match from the list of drag
47938      * and drop objects returned by the drag and drop events when we are
47939      * in INTERSECT mode.  It returns either the first object that the
47940      * cursor is over, or the object that has the greatest overlap with
47941      * the dragged element.
47942      * @method getBestMatch
47943      * @param  {Ext.dd.DragDrop[]} dds The array of drag and drop objects
47944      * targeted
47945      * @return {Ext.dd.DragDrop}       The best single match
47946      */
47947     getBestMatch: function(dds) {
47948         var winner = null;
47949         // Return null if the input is not what we expect
47950         //if (!dds || !dds.length || dds.length == 0) {
47951            // winner = null;
47952         // If there is only one item, it wins
47953         //} else if (dds.length == 1) {
47954
47955         var len = dds.length;
47956
47957         if (len == 1) {
47958             winner = dds[0];
47959         } else {
47960             // Loop through the targeted items
47961             for (var i=0; i<len; ++i) {
47962                 var dd = dds[i];
47963                 // If the cursor is over the object, it wins.  If the
47964                 // cursor is over multiple matches, the first one we come
47965                 // to wins.
47966                 if (dd.cursorIsOver) {
47967                     winner = dd;
47968                     break;
47969                 // Otherwise the object with the most overlap wins
47970                 } else {
47971                     if (!winner ||
47972                         winner.overlap.getArea() < dd.overlap.getArea()) {
47973                         winner = dd;
47974                     }
47975                 }
47976             }
47977         }
47978
47979         return winner;
47980     },
47981
47982     /**
47983      * Refreshes the cache of the top-left and bottom-right points of the
47984      * drag and drop objects in the specified group(s).  This is in the
47985      * format that is stored in the drag and drop instance, so typical
47986      * usage is:
47987      * <code>
47988      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
47989      * </code>
47990      * Alternatively:
47991      * <code>
47992      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
47993      * </code>
47994      * @TODO this really should be an indexed array.  Alternatively this
47995      * method could accept both.
47996      * @method refreshCache
47997      * @param {Object} groups an associative array of groups to refresh
47998      */
47999     refreshCache: function(groups) {
48000         for (var sGroup in groups) {
48001             if ("string" != typeof sGroup) {
48002                 continue;
48003             }
48004             for (var i in this.ids[sGroup]) {
48005                 var oDD = this.ids[sGroup][i];
48006
48007                 if (this.isTypeOfDD(oDD)) {
48008                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
48009                     var loc = this.getLocation(oDD);
48010                     if (loc) {
48011                         this.locationCache[oDD.id] = loc;
48012                     } else {
48013                         delete this.locationCache[oDD.id];
48014                         // this will unregister the drag and drop object if
48015                         // the element is not in a usable state
48016                         // oDD.unreg();
48017                     }
48018                 }
48019             }
48020         }
48021     },
48022
48023     /**
48024      * This checks to make sure an element exists and is in the DOM.  The
48025      * main purpose is to handle cases where innerHTML is used to remove
48026      * drag and drop objects from the DOM.  IE provides an 'unspecified
48027      * error' when trying to access the offsetParent of such an element
48028      * @method verifyEl
48029      * @param {HTMLElement} el the element to check
48030      * @return {Boolean} true if the element looks usable
48031      */
48032     verifyEl: function(el) {
48033         if (el) {
48034             var parent;
48035             if(Ext.isIE){
48036                 try{
48037                     parent = el.offsetParent;
48038                 }catch(e){}
48039             }else{
48040                 parent = el.offsetParent;
48041             }
48042             if (parent) {
48043                 return true;
48044             }
48045         }
48046
48047         return false;
48048     },
48049
48050     /**
48051      * Returns a Region object containing the drag and drop element's position
48052      * and size, including the padding configured for it
48053      * @method getLocation
48054      * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
48055      * @return {Ext.util.Region} a Region object representing the total area
48056      * the element occupies, including any padding
48057      * the instance is configured for.
48058      */
48059     getLocation: function(oDD) {
48060         if (! this.isTypeOfDD(oDD)) {
48061             return null;
48062         }
48063
48064         //delegate getLocation method to the
48065         //drag and drop target.
48066         if (oDD.getRegion) {
48067             return oDD.getRegion();
48068         }
48069
48070         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
48071
48072         try {
48073             pos= Ext.Element.getXY(el);
48074         } catch (e) { }
48075
48076         if (!pos) {
48077             return null;
48078         }
48079
48080         x1 = pos[0];
48081         x2 = x1 + el.offsetWidth;
48082         y1 = pos[1];
48083         y2 = y1 + el.offsetHeight;
48084
48085         t = y1 - oDD.padding[0];
48086         r = x2 + oDD.padding[1];
48087         b = y2 + oDD.padding[2];
48088         l = x1 - oDD.padding[3];
48089
48090         return Ext.create('Ext.util.Region', t, r, b, l);
48091     },
48092
48093     /**
48094      * Checks the cursor location to see if it over the target
48095      * @method isOverTarget
48096      * @param {Ext.util.Point} pt The point to evaluate
48097      * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
48098      * @return {Boolean} true if the mouse is over the target
48099      * @private
48100      */
48101     isOverTarget: function(pt, oTarget, intersect) {
48102         // use cache if available
48103         var loc = this.locationCache[oTarget.id];
48104         if (!loc || !this.useCache) {
48105             loc = this.getLocation(oTarget);
48106             this.locationCache[oTarget.id] = loc;
48107
48108         }
48109
48110         if (!loc) {
48111             return false;
48112         }
48113
48114         oTarget.cursorIsOver = loc.contains( pt );
48115
48116         // DragDrop is using this as a sanity check for the initial mousedown
48117         // in this case we are done.  In POINT mode, if the drag obj has no
48118         // contraints, we are also done. Otherwise we need to evaluate the
48119         // location of the target as related to the actual location of the
48120         // dragged element.
48121         var dc = this.dragCurrent;
48122         if (!dc || !dc.getTargetCoord ||
48123                 (!intersect && !dc.constrainX && !dc.constrainY)) {
48124             return oTarget.cursorIsOver;
48125         }
48126
48127         oTarget.overlap = null;
48128
48129         // Get the current location of the drag element, this is the
48130         // location of the mouse event less the delta that represents
48131         // where the original mousedown happened on the element.  We
48132         // need to consider constraints and ticks as well.
48133         var pos = dc.getTargetCoord(pt.x, pt.y);
48134
48135         var el = dc.getDragEl();
48136         var curRegion = Ext.create('Ext.util.Region', pos.y,
48137                                                pos.x + el.offsetWidth,
48138                                                pos.y + el.offsetHeight,
48139                                                pos.x );
48140
48141         var overlap = curRegion.intersect(loc);
48142
48143         if (overlap) {
48144             oTarget.overlap = overlap;
48145             return (intersect) ? true : oTarget.cursorIsOver;
48146         } else {
48147             return false;
48148         }
48149     },
48150
48151     /**
48152      * unload event handler
48153      * @method _onUnload
48154      * @private
48155      */
48156     _onUnload: function(e, me) {
48157         Ext.dd.DragDropManager.unregAll();
48158     },
48159
48160     /**
48161      * Cleans up the drag and drop events and objects.
48162      * @method unregAll
48163      * @private
48164      */
48165     unregAll: function() {
48166
48167         if (this.dragCurrent) {
48168             this.stopDrag();
48169             this.dragCurrent = null;
48170         }
48171
48172         this._execOnAll("unreg", []);
48173
48174         for (var i in this.elementCache) {
48175             delete this.elementCache[i];
48176         }
48177
48178         this.elementCache = {};
48179         this.ids = {};
48180     },
48181
48182     /**
48183      * A cache of DOM elements
48184      * @property elementCache
48185      * @private
48186      */
48187     elementCache: {},
48188
48189     /**
48190      * Get the wrapper for the DOM element specified
48191      * @method getElWrapper
48192      * @param {String} id the id of the element to get
48193      * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
48194      * @private
48195      * @deprecated This wrapper isn't that useful
48196      */
48197     getElWrapper: function(id) {
48198         var oWrapper = this.elementCache[id];
48199         if (!oWrapper || !oWrapper.el) {
48200             oWrapper = this.elementCache[id] =
48201                 new this.ElementWrapper(Ext.getDom(id));
48202         }
48203         return oWrapper;
48204     },
48205
48206     /**
48207      * Returns the actual DOM element
48208      * @method getElement
48209      * @param {String} id the id of the elment to get
48210      * @return {Object} The element
48211      * @deprecated use Ext.lib.Ext.getDom instead
48212      */
48213     getElement: function(id) {
48214         return Ext.getDom(id);
48215     },
48216
48217     /**
48218      * Returns the style property for the DOM element (i.e.,
48219      * document.getElById(id).style)
48220      * @method getCss
48221      * @param {String} id the id of the elment to get
48222      * @return {Object} The style property of the element
48223      */
48224     getCss: function(id) {
48225         var el = Ext.getDom(id);
48226         return (el) ? el.style : null;
48227     },
48228
48229     /**
48230      * @class Ext.dd.DragDropManager.ElementWrapper
48231      * Inner class for cached elements
48232      * @private
48233      * @deprecated
48234      */
48235     ElementWrapper: function(el) {
48236         /**
48237          * The element
48238          * @property el
48239          */
48240         this.el = el || null;
48241         /**
48242          * The element id
48243          * @property id
48244          */
48245         this.id = this.el && el.id;
48246         /**
48247          * A reference to the style property
48248          * @property css
48249          */
48250         this.css = this.el && el.style;
48251     },
48252
48253     // The DragDropManager class continues
48254     /** @class Ext.dd.DragDropManager */
48255
48256     /**
48257      * Returns the X position of an html element
48258      * @param {HTMLElement} el the element for which to get the position
48259      * @return {Number} the X coordinate
48260      */
48261     getPosX: function(el) {
48262         return Ext.Element.getX(el);
48263     },
48264
48265     /**
48266      * Returns the Y position of an html element
48267      * @param {HTMLElement} el the element for which to get the position
48268      * @return {Number} the Y coordinate
48269      */
48270     getPosY: function(el) {
48271         return Ext.Element.getY(el);
48272     },
48273
48274     /**
48275      * Swap two nodes.  In IE, we use the native method, for others we
48276      * emulate the IE behavior
48277      * @param {HTMLElement} n1 the first node to swap
48278      * @param {HTMLElement} n2 the other node to swap
48279      */
48280     swapNode: function(n1, n2) {
48281         if (n1.swapNode) {
48282             n1.swapNode(n2);
48283         } else {
48284             var p = n2.parentNode;
48285             var s = n2.nextSibling;
48286
48287             if (s == n1) {
48288                 p.insertBefore(n1, n2);
48289             } else if (n2 == n1.nextSibling) {
48290                 p.insertBefore(n2, n1);
48291             } else {
48292                 n1.parentNode.replaceChild(n2, n1);
48293                 p.insertBefore(n1, s);
48294             }
48295         }
48296     },
48297
48298     /**
48299      * Returns the current scroll position
48300      * @private
48301      */
48302     getScroll: function () {
48303         var doc   = window.document,
48304             docEl = doc.documentElement,
48305             body  = doc.body,
48306             top   = 0,
48307             left  = 0;
48308
48309         if (Ext.isGecko4) {
48310             top  = window.scrollYOffset;
48311             left = window.scrollXOffset;
48312         } else {
48313             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
48314                 top  = docEl.scrollTop;
48315                 left = docEl.scrollLeft;
48316             } else if (body) {
48317                 top  = body.scrollTop;
48318                 left = body.scrollLeft;
48319             }
48320         }
48321         return {
48322             top: top,
48323             left: left
48324         };
48325     },
48326
48327     /**
48328      * Returns the specified element style property
48329      * @param {HTMLElement} el          the element
48330      * @param {String}      styleProp   the style property
48331      * @return {String} The value of the style property
48332      */
48333     getStyle: function(el, styleProp) {
48334         return Ext.fly(el).getStyle(styleProp);
48335     },
48336
48337     /**
48338      * Gets the scrollTop
48339      * @return {Number} the document's scrollTop
48340      */
48341     getScrollTop: function () {
48342         return this.getScroll().top;
48343     },
48344
48345     /**
48346      * Gets the scrollLeft
48347      * @return {Number} the document's scrollTop
48348      */
48349     getScrollLeft: function () {
48350         return this.getScroll().left;
48351     },
48352
48353     /**
48354      * Sets the x/y position of an element to the location of the
48355      * target element.
48356      * @param {HTMLElement} moveEl      The element to move
48357      * @param {HTMLElement} targetEl    The position reference element
48358      */
48359     moveToEl: function (moveEl, targetEl) {
48360         var aCoord = Ext.Element.getXY(targetEl);
48361         Ext.Element.setXY(moveEl, aCoord);
48362     },
48363
48364     /**
48365      * Numeric array sort function
48366      * @param {Number} a
48367      * @param {Number} b
48368      * @returns {Number} positive, negative or 0
48369      */
48370     numericSort: function(a, b) {
48371         return (a - b);
48372     },
48373
48374     /**
48375      * Internal counter
48376      * @property {Number} _timeoutCount
48377      * @private
48378      */
48379     _timeoutCount: 0,
48380
48381     /**
48382      * Trying to make the load order less important.  Without this we get
48383      * an error if this file is loaded before the Event Utility.
48384      * @private
48385      */
48386     _addListeners: function() {
48387         if ( document ) {
48388             this._onLoad();
48389         } else {
48390             if (this._timeoutCount > 2000) {
48391             } else {
48392                 setTimeout(this._addListeners, 10);
48393                 if (document && document.body) {
48394                     this._timeoutCount += 1;
48395                 }
48396             }
48397         }
48398     },
48399
48400     /**
48401      * Recursively searches the immediate parent and all child nodes for
48402      * the handle element in order to determine wheter or not it was
48403      * clicked.
48404      * @param {HTMLElement} node the html element to inspect
48405      */
48406     handleWasClicked: function(node, id) {
48407         if (this.isHandle(id, node.id)) {
48408             return true;
48409         } else {
48410             // check to see if this is a text node child of the one we want
48411             var p = node.parentNode;
48412
48413             while (p) {
48414                 if (this.isHandle(id, p.id)) {
48415                     return true;
48416                 } else {
48417                     p = p.parentNode;
48418                 }
48419             }
48420         }
48421
48422         return false;
48423     }
48424 }, function() {
48425     this._addListeners();
48426 });
48427
48428 /**
48429  * @class Ext.layout.container.Box
48430  * @extends Ext.layout.container.Container
48431  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
48432  */
48433
48434 Ext.define('Ext.layout.container.Box', {
48435
48436     /* Begin Definitions */
48437
48438     alias: ['layout.box'],
48439     extend: 'Ext.layout.container.Container',
48440     alternateClassName: 'Ext.layout.BoxLayout',
48441
48442     requires: [
48443         'Ext.layout.container.boxOverflow.None',
48444         'Ext.layout.container.boxOverflow.Menu',
48445         'Ext.layout.container.boxOverflow.Scroller',
48446         'Ext.util.Format',
48447         'Ext.dd.DragDropManager'
48448     ],
48449
48450     /* End Definitions */
48451
48452     /**
48453      * @cfg {Boolean/Number/Object} animate
48454      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
48455      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
48456      * <p>May be set as a property at any time.</p>
48457      */
48458
48459     /**
48460      * @cfg {Object} defaultMargins
48461      * <p>If the individual contained items do not have a <tt>margins</tt>
48462      * property specified or margin specified via CSS, the default margins from this property will be
48463      * applied to each item.</p>
48464      * <br><p>This property may be specified as an object containing margins
48465      * to apply in the format:</p><pre><code>
48466 {
48467     top: (top margin),
48468     right: (right margin),
48469     bottom: (bottom margin),
48470     left: (left margin)
48471 }</code></pre>
48472      * <p>This property may also be specified as a string containing
48473      * space-separated, numeric margin values. The order of the sides associated
48474      * with each value matches the way CSS processes margin values:</p>
48475      * <div class="mdetail-params"><ul>
48476      * <li>If there is only one value, it applies to all sides.</li>
48477      * <li>If there are two values, the top and bottom borders are set to the
48478      * first value and the right and left are set to the second.</li>
48479      * <li>If there are three values, the top is set to the first value, the left
48480      * and right are set to the second, and the bottom is set to the third.</li>
48481      * <li>If there are four values, they apply to the top, right, bottom, and
48482      * left, respectively.</li>
48483      * </ul></div>
48484      */
48485     defaultMargins: {
48486         top: 0,
48487         right: 0,
48488         bottom: 0,
48489         left: 0
48490     },
48491
48492     /**
48493      * @cfg {String} padding
48494      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
48495      * <p>This property must be specified as a string containing
48496      * space-separated, numeric padding values. The order of the sides associated
48497      * with each value matches the way CSS processes padding values:</p>
48498      * <div class="mdetail-params"><ul>
48499      * <li>If there is only one value, it applies to all sides.</li>
48500      * <li>If there are two values, the top and bottom borders are set to the
48501      * first value and the right and left are set to the second.</li>
48502      * <li>If there are three values, the top is set to the first value, the left
48503      * and right are set to the second, and the bottom is set to the third.</li>
48504      * <li>If there are four values, they apply to the top, right, bottom, and
48505      * left, respectively.</li>
48506      * </ul></div>
48507      */
48508     padding: '0',
48509     // documented in subclasses
48510     pack: 'start',
48511
48512     /**
48513      * @cfg {String} pack
48514      * Controls how the child items of the container are packed together. Acceptable configuration values
48515      * for this property are:
48516      * <div class="mdetail-params"><ul>
48517      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
48518      * <b>left</b> side of container</div></li>
48519      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
48520      * <b>mid-width</b> of container</div></li>
48521      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
48522      * side of container</div></li>
48523      * </ul></div>
48524      */
48525     /**
48526      * @cfg {Number} flex
48527      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
48528      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
48529      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
48530      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
48531      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
48532      */
48533
48534     type: 'box',
48535     scrollOffset: 0,
48536     itemCls: Ext.baseCSSPrefix + 'box-item',
48537     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
48538     innerCls: Ext.baseCSSPrefix + 'box-inner',
48539
48540     bindToOwnerCtContainer: true,
48541
48542     // availableSpaceOffset is used to adjust the availableWidth, typically used
48543     // to reserve space for a scrollbar
48544     availableSpaceOffset: 0,
48545
48546     // whether or not to reserve the availableSpaceOffset in layout calculations
48547     reserveOffset: true,
48548
48549     /**
48550      * @cfg {Boolean} shrinkToFit
48551      * True (the default) to allow fixed size components to shrink (limited to their
48552      * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
48553      * overflow.
48554      */
48555     shrinkToFit: true,
48556
48557     /**
48558      * @cfg {Boolean} clearInnerCtOnLayout
48559      */
48560     clearInnerCtOnLayout: false,
48561
48562     flexSortFn: function (a, b) {
48563         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
48564             infiniteValue = Infinity;
48565         a = a.component[maxParallelPrefix] || infiniteValue;
48566         b = b.component[maxParallelPrefix] || infiniteValue;
48567         // IE 6/7 Don't like Infinity - Infinity...
48568         if (!isFinite(a) && !isFinite(b)) {
48569             return false;
48570         }
48571         return a - b;
48572     },
48573
48574     // Sort into *descending* order.
48575     minSizeSortFn: function(a, b) {
48576         return b.available - a.available;
48577     },
48578
48579     constructor: function(config) {
48580         var me = this;
48581
48582         me.callParent(arguments);
48583
48584         // The sort function needs access to properties in this, so must be bound.
48585         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
48586
48587         me.initOverflowHandler();
48588     },
48589
48590     /**
48591      * @private
48592      * Returns the current size and positioning of the passed child item.
48593      * @param {Ext.Component} child The child Component to calculate the box for
48594      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
48595      */
48596     getChildBox: function(child) {
48597         child = child.el || this.owner.getComponent(child).el;
48598         var size = child.getBox(false, true);
48599         return {
48600             left: size.left,
48601             top: size.top,
48602             width: size.width,
48603             height: size.height
48604         };
48605     },
48606
48607     /**
48608      * @private
48609      * Calculates the size and positioning of the passed child item.
48610      * @param {Ext.Component} child The child Component to calculate the box for
48611      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
48612      */
48613     calculateChildBox: function(child) {
48614         var me = this,
48615             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
48616             ln = boxes.length,
48617             i = 0;
48618
48619         child = me.owner.getComponent(child);
48620         for (; i < ln; i++) {
48621             if (boxes[i].component === child) {
48622                 return boxes[i];
48623             }
48624         }
48625     },
48626
48627     /**
48628      * @private
48629      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
48630      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
48631      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
48632      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
48633      * @param {Object} targetSize Object containing target size and height
48634      * @return {Object} Object containing box measurements for each child, plus meta data
48635      */
48636     calculateChildBoxes: function(visibleItems, targetSize) {
48637         var me = this,
48638             math = Math,
48639             mmax = math.max,
48640             infiniteValue = Infinity,
48641             undefinedValue,
48642
48643             parallelPrefix = me.parallelPrefix,
48644             parallelPrefixCap = me.parallelPrefixCap,
48645             perpendicularPrefix = me.perpendicularPrefix,
48646             perpendicularPrefixCap = me.perpendicularPrefixCap,
48647             parallelMinString = 'min' + parallelPrefixCap,
48648             perpendicularMinString = 'min' + perpendicularPrefixCap,
48649             perpendicularMaxString = 'max' + perpendicularPrefixCap,
48650
48651             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
48652             perpendicularSize = targetSize[perpendicularPrefix],
48653             padding = me.padding,
48654             parallelOffset = padding[me.parallelBefore],
48655             paddingParallel = parallelOffset + padding[me.parallelAfter],
48656             perpendicularOffset = padding[me.perpendicularLeftTop],
48657             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
48658             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
48659
48660             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
48661
48662             isStart = me.pack == 'start',
48663             isCenter = me.pack == 'center',
48664             isEnd = me.pack == 'end',
48665
48666             constrain = Ext.Number.constrain,
48667             visibleCount = visibleItems.length,
48668             nonFlexSize = 0,
48669             totalFlex = 0,
48670             desiredSize = 0,
48671             minimumSize = 0,
48672             maxSize = 0,
48673             boxes = [],
48674             minSizes = [],
48675             calculatedWidth,
48676
48677             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
48678             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
48679             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
48680             perpendicularMargins, stretchSize;
48681
48682         //gather the total flex of all flexed items and the width taken up by fixed width items
48683         for (i = 0; i < visibleCount; i++) {
48684             child = visibleItems[i];
48685             childPerpendicular = child[perpendicularPrefix];
48686             if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
48687                 if (child.componentLayout.initialized !== true) {
48688                     me.layoutItem(child);
48689                 }
48690             }
48691
48692             childMargins = child.margins;
48693             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
48694
48695             // Create the box description object for this child item.
48696             tmpObj = {
48697                 component: child,
48698                 margins: childMargins
48699             };
48700
48701             // flex and not 'auto' width
48702             if (child.flex) {
48703                 totalFlex += child.flex;
48704                 childParallel = undefinedValue;
48705             }
48706             // Not flexed or 'auto' width or undefined width
48707             else {
48708                 if (!(child[parallelPrefix] && childPerpendicular)) {
48709                     childSize = child.getSize();
48710                 }
48711                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
48712                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
48713             }
48714
48715             nonFlexSize += parallelMargins + (childParallel || 0);
48716             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
48717             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
48718
48719             // Max height for align - force layout of non-laid out subcontainers without a numeric height
48720             if (typeof childPerpendicular != 'number') {
48721                 // Clear any static sizing and revert to flow so we can get a proper measurement
48722                 // child['set' + perpendicularPrefixCap](null);
48723                 childPerpendicular = child['get' + perpendicularPrefixCap]();
48724             }
48725
48726             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
48727             // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
48728             maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
48729
48730             tmpObj[parallelPrefix] = childParallel || undefinedValue;
48731             tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
48732             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
48733             boxes.push(tmpObj);
48734         }
48735
48736         // Only calculate parallel overflow indicators if we are not auto sizing
48737         if (!me.autoSize) {
48738             shortfall = desiredSize - parallelSize;
48739             tooNarrow = minimumSize > parallelSize;
48740         }
48741
48742         //the space available to the flexed items
48743         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
48744
48745         if (tooNarrow) {
48746             for (i = 0; i < visibleCount; i++) {
48747                 box = boxes[i];
48748                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
48749                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48750                 box[parallelPrefix] = minSize;
48751             }
48752         }
48753         else {
48754             //all flexed items should be sized to their minimum size, other items should be shrunk down until
48755             //the shortfall has been accounted for
48756             if (shortfall > 0) {
48757                 /*
48758                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
48759                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
48760                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
48761                  */
48762                 for (i = 0; i < visibleCount; i++) {
48763                     item = visibleItems[i];
48764                     minSize = item[parallelMinString] || 0;
48765
48766                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
48767                     //shrunk to their minSize because they're flexible and should be the first to lose size
48768                     if (item.flex) {
48769                         box = boxes[i];
48770                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48771                         box[parallelPrefix] = minSize;
48772                     } else if (me.shrinkToFit) {
48773                         minSizes.push({
48774                             minSize: minSize,
48775                             available: boxes[i][parallelPrefix] - minSize,
48776                             index: i
48777                         });
48778                     }
48779                 }
48780
48781                 //sort by descending amount of width remaining before minWidth is reached
48782                 Ext.Array.sort(minSizes, me.minSizeSortFn);
48783
48784                 /*
48785                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
48786                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
48787                  * smallest difference between their size and minSize first, so that if reducing the size by the average
48788                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
48789                  */
48790                 for (i = 0, length = minSizes.length; i < length; i++) {
48791                     itemIndex = minSizes[i].index;
48792
48793                     if (itemIndex == undefinedValue) {
48794                         continue;
48795                     }
48796                     item = visibleItems[itemIndex];
48797                     minSize = minSizes[i].minSize;
48798
48799                     box = boxes[itemIndex];
48800                     oldSize = box[parallelPrefix];
48801                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
48802                     reduction = oldSize - newSize;
48803
48804                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
48805                     box[parallelPrefix] = newSize;
48806                     shortfall -= reduction;
48807                 }
48808                 tooNarrow = (shortfall > 0);
48809             }
48810             else {
48811                 remainingSpace = availableSpace;
48812                 remainingFlex = totalFlex;
48813                 flexedBoxes = [];
48814
48815                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
48816                 for (i = 0; i < visibleCount; i++) {
48817                     child = visibleItems[i];
48818                     if (isStart && child.flex) {
48819                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
48820                     }
48821                 }
48822                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
48823                 // so that unallocated space caused by maxWidth being less than flexed width
48824                 // can be reallocated to subsequent flexed boxes.
48825                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
48826
48827                 // Calculate the size of each flexed item, and attempt to set it.
48828                 for (i = 0; i < flexedBoxes.length; i++) {
48829                     calcs = flexedBoxes[i];
48830                     child = calcs.component;
48831                     childMargins = calcs.margins;
48832
48833                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
48834
48835                     // Implement maxSize and minSize check
48836                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
48837
48838                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
48839                     remainingSpace -= flexedSize;
48840                     remainingFlex -= child.flex;
48841
48842                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
48843                     calcs[parallelPrefix] = flexedSize;
48844                 }
48845             }
48846         }
48847
48848         if (isCenter) {
48849             parallelOffset += availableSpace / 2;
48850         }
48851         else if (isEnd) {
48852             parallelOffset += availableSpace;
48853         }
48854
48855         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
48856         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
48857         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
48858         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
48859         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
48860
48861             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
48862             if (me.owner.frameSize) {
48863                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
48864             }
48865             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
48866             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
48867         }
48868
48869         //finally, calculate the left and top position of each item
48870         for (i = 0; i < visibleCount; i++) {
48871             child = visibleItems[i];
48872             calcs = boxes[i];
48873
48874             childMargins = calcs.margins;
48875
48876             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
48877
48878             // Advance past the "before" margin
48879             parallelOffset += childMargins[me.parallelBefore];
48880
48881             calcs[me.parallelBefore] = parallelOffset;
48882             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
48883
48884             if (me.align == 'stretch') {
48885                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48886                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48887                 calcs[perpendicularPrefix] = stretchSize;
48888             }
48889             else if (me.align == 'stretchmax') {
48890                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48891                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48892                 calcs[perpendicularPrefix] = stretchSize;
48893             }
48894             else if (me.align == me.alignCenteringString) {
48895                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
48896                 // the size to yield the space available to center within.
48897                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
48898                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
48899                 if (diff > 0) {
48900                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
48901                 }
48902             }
48903
48904             // Advance past the box size and the "after" margin
48905             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
48906         }
48907
48908         return {
48909             boxes: boxes,
48910             meta : {
48911                 calculatedWidth: calculatedWidth,
48912                 maxSize: maxSize,
48913                 nonFlexSize: nonFlexSize,
48914                 desiredSize: desiredSize,
48915                 minimumSize: minimumSize,
48916                 shortfall: shortfall,
48917                 tooNarrow: tooNarrow
48918             }
48919         };
48920     },
48921
48922     onRemove: function(comp){
48923         this.callParent(arguments);
48924         if (this.overflowHandler) {
48925             this.overflowHandler.onRemove(comp);
48926         }
48927     },
48928
48929     /**
48930      * @private
48931      */
48932     initOverflowHandler: function() {
48933         var handler = this.overflowHandler;
48934
48935         if (typeof handler == 'string') {
48936             handler = {
48937                 type: handler
48938             };
48939         }
48940
48941         var handlerType = 'None';
48942         if (handler && handler.type !== undefined) {
48943             handlerType = handler.type;
48944         }
48945
48946         var constructor = Ext.layout.container.boxOverflow[handlerType];
48947         if (constructor[this.type]) {
48948             constructor = constructor[this.type];
48949         }
48950
48951         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
48952     },
48953
48954     /**
48955      * @private
48956      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
48957      * when laying out
48958      */
48959     onLayout: function() {
48960         this.callParent();
48961         // Clear the innerCt size so it doesn't influence the child items.
48962         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
48963             this.innerCt.setSize(null, null);
48964         }
48965
48966         var me = this,
48967             targetSize = me.getLayoutTargetSize(),
48968             items = me.getVisibleItems(),
48969             calcs = me.calculateChildBoxes(items, targetSize),
48970             boxes = calcs.boxes,
48971             meta = calcs.meta,
48972             handler, method, results;
48973
48974         if (me.autoSize && calcs.meta.desiredSize) {
48975             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
48976         }
48977
48978         //invoke the overflow handler, if one is configured
48979         if (meta.shortfall > 0) {
48980             handler = me.overflowHandler;
48981             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
48982
48983             results = handler[method](calcs, targetSize);
48984
48985             if (results) {
48986                 if (results.targetSize) {
48987                     targetSize = results.targetSize;
48988                 }
48989
48990                 if (results.recalculate) {
48991                     items = me.getVisibleItems();
48992                     calcs = me.calculateChildBoxes(items, targetSize);
48993                     boxes = calcs.boxes;
48994                 }
48995             }
48996         } else {
48997             me.overflowHandler.clearOverflow();
48998         }
48999
49000         /**
49001          * @private
49002          * @property layoutTargetLastSize
49003          * @type Object
49004          * Private cache of the last measured size of the layout target. This should never be used except by
49005          * BoxLayout subclasses during their onLayout run.
49006          */
49007         me.layoutTargetLastSize = targetSize;
49008
49009         /**
49010          * @private
49011          * @property childBoxCache
49012          * @type Array
49013          * Array of the last calculated height, width, top and left positions of each visible rendered component
49014          * within the Box layout.
49015          */
49016         me.childBoxCache = calcs;
49017
49018         me.updateInnerCtSize(targetSize, calcs);
49019         me.updateChildBoxes(boxes);
49020         me.handleTargetOverflow(targetSize);
49021     },
49022     
49023     animCallback: Ext.emptyFn,
49024
49025     /**
49026      * Resizes and repositions each child component
49027      * @param {Object[]} boxes The box measurements
49028      */
49029     updateChildBoxes: function(boxes) {
49030         var me = this,
49031             i = 0,
49032             length = boxes.length,
49033             animQueue = [],
49034             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
49035             oldBox, newBox, changed, comp, boxAnim, animCallback;
49036
49037         for (; i < length; i++) {
49038             newBox = boxes[i];
49039             comp = newBox.component;
49040
49041             // If a Component is being drag/dropped, skip positioning it.
49042             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
49043             if (dd && (dd.getDragEl() === comp.el.dom)) {
49044                 continue;
49045             }
49046
49047             changed = false;
49048
49049             oldBox = me.getChildBox(comp);
49050
49051             // If we are animating, we build up an array of Anim config objects, one for each
49052             // child Component which has any changed box properties. Those with unchanged
49053             // properties are not animated.
49054             if (me.animate) {
49055                 // Animate may be a config object containing callback.
49056                 animCallback = me.animate.callback || me.animate;
49057                 boxAnim = {
49058                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
49059                     target: comp,
49060                     from: {},
49061                     to: {},
49062                     listeners: {}
49063                 };
49064                 // Only set from and to properties when there's a change.
49065                 // Perform as few Component setter methods as possible.
49066                 // Temporarily set the property values that we are not animating
49067                 // so that doComponentLayout does not auto-size them.
49068                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
49069                     changed = true;
49070                     // boxAnim.from.width = oldBox.width;
49071                     boxAnim.to.width = newBox.width;
49072                 }
49073                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
49074                     changed = true;
49075                     // boxAnim.from.height = oldBox.height;
49076                     boxAnim.to.height = newBox.height;
49077                 }
49078                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
49079                     changed = true;
49080                     // boxAnim.from.left = oldBox.left;
49081                     boxAnim.to.left = newBox.left;
49082                 }
49083                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
49084                     changed = true;
49085                     // boxAnim.from.top = oldBox.top;
49086                     boxAnim.to.top = newBox.top;
49087                 }
49088                 if (changed) {
49089                     animQueue.push(boxAnim);
49090                 }
49091             } else {
49092                 if (newBox.dirtySize) {
49093                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
49094                         me.setItemSize(comp, newBox.width, newBox.height);
49095                     }
49096                 }
49097                 // Don't set positions to NaN
49098                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
49099                     continue;
49100                 }
49101                 comp.setPosition(newBox.left, newBox.top);
49102             }
49103         }
49104
49105         // Kick off any queued animations
49106         length = animQueue.length;
49107         if (length) {
49108
49109             // A function which cleans up when a Component's animation is done.
49110             // The last one to finish calls the callback.
49111             var afterAnimate = function(anim) {
49112                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
49113                 length -= 1;
49114                 if (!length) {
49115                     me.animCallback(anim);
49116                     me.layoutBusy = false;
49117                     if (Ext.isFunction(animCallback)) {
49118                         animCallback();
49119                     }
49120                 }
49121             };
49122
49123             var beforeAnimate = function() {
49124                 me.layoutBusy = true;
49125             };
49126
49127             // Start each box animation off
49128             for (i = 0, length = animQueue.length; i < length; i++) {
49129                 boxAnim = animQueue[i];
49130
49131                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
49132                 boxAnim.listeners.afteranimate = afterAnimate;
49133
49134                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
49135                 if (!i) {
49136                     boxAnim.listeners.beforeanimate = beforeAnimate;
49137                 }
49138                 if (me.animate.duration) {
49139                     boxAnim.duration = me.animate.duration;
49140                 }
49141                 comp = boxAnim.target;
49142                 delete boxAnim.target;
49143                 // Stop any currently running animation
49144                 comp.stopAnimation();
49145                 comp.animate(boxAnim);
49146             }
49147         }
49148     },
49149
49150     /**
49151      * @private
49152      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
49153      * to make sure all child items fit within it. We call this before sizing the children because if our child
49154      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
49155      * again immediately afterwards, giving a performance hit.
49156      * Subclasses should provide an implementation.
49157      * @param {Object} currentSize The current height and width of the innerCt
49158      * @param {Object} calculations The new box calculations of all items to be laid out
49159      */
49160     updateInnerCtSize: function(tSize, calcs) {
49161         var me = this,
49162             mmax = Math.max,
49163             align = me.align,
49164             padding = me.padding,
49165             width = tSize.width,
49166             height = tSize.height,
49167             meta = calcs.meta,
49168             innerCtWidth,
49169             innerCtHeight;
49170
49171         if (me.direction == 'horizontal') {
49172             innerCtWidth = width;
49173             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
49174
49175             if (align == 'stretch') {
49176                 innerCtHeight = height;
49177             }
49178             else if (align == 'middle') {
49179                 innerCtHeight = mmax(height, innerCtHeight);
49180             }
49181         } else {
49182             innerCtHeight = height;
49183             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
49184
49185             if (align == 'stretch') {
49186                 innerCtWidth = width;
49187             }
49188             else if (align == 'center') {
49189                 innerCtWidth = mmax(width, innerCtWidth);
49190             }
49191         }
49192         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
49193
49194         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
49195         // then, if the Component has not assumed the size of its content, set it to do so.
49196         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
49197             me.owner.el.setWidth(meta.calculatedWidth);
49198         }
49199
49200         if (me.innerCt.dom.scrollTop) {
49201             me.innerCt.dom.scrollTop = 0;
49202         }
49203     },
49204
49205     /**
49206      * @private
49207      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
49208      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
49209      * target. Having a Box layout inside such a target is therefore not recommended.
49210      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
49211      * @param {Ext.container.Container} container The container
49212      * @param {Ext.Element} target The target element
49213      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
49214      */
49215     handleTargetOverflow: function(previousTargetSize) {
49216         var target = this.getTarget(),
49217             overflow = target.getStyle('overflow'),
49218             newTargetSize;
49219
49220         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
49221             newTargetSize = this.getLayoutTargetSize();
49222             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
49223                 this.adjustmentPass = true;
49224                 this.onLayout();
49225                 return true;
49226             }
49227         }
49228
49229         delete this.adjustmentPass;
49230     },
49231
49232     // private
49233     isValidParent : function(item, target, position) {
49234         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
49235         // We only care whether the item is a direct child of the innerCt element.
49236         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
49237         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
49238     },
49239
49240     // Overridden method from AbstractContainer.
49241     // Used in the base AbstractLayout.beforeLayout method to render all items into.
49242     getRenderTarget: function() {
49243         if (!this.innerCt) {
49244             // the innerCt prevents wrapping and shuffling while the container is resizing
49245             this.innerCt = this.getTarget().createChild({
49246                 cls: this.innerCls,
49247                 role: 'presentation'
49248             });
49249             this.padding = Ext.util.Format.parseBox(this.padding);
49250         }
49251         return this.innerCt;
49252     },
49253
49254     // private
49255     renderItem: function(item, target) {
49256         this.callParent(arguments);
49257         var me = this,
49258             itemEl = item.getEl(),
49259             style = itemEl.dom.style,
49260             margins = item.margins || item.margin;
49261
49262         // Parse the item's margin/margins specification
49263         if (margins) {
49264             if (Ext.isString(margins) || Ext.isNumber(margins)) {
49265                 margins = Ext.util.Format.parseBox(margins);
49266             } else {
49267                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
49268             }
49269         } else {
49270             margins = Ext.apply({}, me.defaultMargins);
49271         }
49272
49273         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
49274         margins.top    += itemEl.getMargin('t');
49275         margins.right  += itemEl.getMargin('r');
49276         margins.bottom += itemEl.getMargin('b');
49277         margins.left   += itemEl.getMargin('l');
49278         margins.height  = margins.top  + margins.bottom;
49279         margins.width   = margins.left + margins.right;
49280         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
49281
49282         // Item must reference calculated margins.
49283         item.margins = margins;
49284     },
49285
49286     /**
49287      * @private
49288      */
49289     destroy: function() {
49290         Ext.destroy(this.innerCt, this.overflowHandler);
49291         this.callParent(arguments);
49292     }
49293 });
49294 /**
49295  * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
49296  * space between child items containing a numeric `flex` configuration.
49297  *
49298  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
49299  *
49300  *     @example
49301  *     Ext.create('Ext.Panel', {
49302  *         width: 500,
49303  *         height: 300,
49304  *         title: "HBoxLayout Panel",
49305  *         layout: {
49306  *             type: 'hbox',
49307  *             align: 'stretch'
49308  *         },
49309  *         renderTo: document.body,
49310  *         items: [{
49311  *             xtype: 'panel',
49312  *             title: 'Inner Panel One',
49313  *             flex: 2
49314  *         },{
49315  *             xtype: 'panel',
49316  *             title: 'Inner Panel Two',
49317  *             flex: 1
49318  *         },{
49319  *             xtype: 'panel',
49320  *             title: 'Inner Panel Three',
49321  *             flex: 1
49322  *         }]
49323  *     });
49324  */
49325 Ext.define('Ext.layout.container.HBox', {
49326
49327     /* Begin Definitions */
49328
49329     alias: ['layout.hbox'],
49330     extend: 'Ext.layout.container.Box',
49331     alternateClassName: 'Ext.layout.HBoxLayout',
49332
49333     /* End Definitions */
49334
49335     /**
49336      * @cfg {String} align
49337      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
49338      *
49339      * - **top** : **Default** child items are aligned vertically at the **top** of the container
49340      * - **middle** : child items are aligned vertically in the **middle** of the container
49341      * - **stretch** : child items are stretched vertically to fill the height of the container
49342      * - **stretchmax** : child items are stretched vertically to the height of the largest item.
49343      */
49344     align: 'top', // top, middle, stretch, strechmax
49345
49346     //@private
49347     alignCenteringString: 'middle',
49348
49349     type : 'hbox',
49350
49351     direction: 'horizontal',
49352
49353     // When creating an argument list to setSize, use this order
49354     parallelSizeIndex: 0,
49355     perpendicularSizeIndex: 1,
49356
49357     parallelPrefix: 'width',
49358     parallelPrefixCap: 'Width',
49359     parallelLT: 'l',
49360     parallelRB: 'r',
49361     parallelBefore: 'left',
49362     parallelBeforeCap: 'Left',
49363     parallelAfter: 'right',
49364     parallelPosition: 'x',
49365
49366     perpendicularPrefix: 'height',
49367     perpendicularPrefixCap: 'Height',
49368     perpendicularLT: 't',
49369     perpendicularRB: 'b',
49370     perpendicularLeftTop: 'top',
49371     perpendicularRightBottom: 'bottom',
49372     perpendicularPosition: 'y',
49373     configureItem: function(item) {
49374         if (item.flex) {
49375             item.layoutManagedWidth = 1;
49376         } else {
49377             item.layoutManagedWidth = 2;
49378         }
49379
49380         if (this.align === 'stretch' || this.align === 'stretchmax') {
49381             item.layoutManagedHeight = 1;
49382         } else {
49383             item.layoutManagedHeight = 2;
49384         }
49385         this.callParent(arguments);
49386     }
49387 });
49388 /**
49389  * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
49390  * between child items containing a numeric `flex` configuration.
49391  *
49392  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
49393  *
49394  *     @example
49395  *     Ext.create('Ext.Panel', {
49396  *         width: 500,
49397  *         height: 400,
49398  *         title: "VBoxLayout Panel",
49399  *         layout: {
49400  *             type: 'vbox',
49401  *             align: 'center'
49402  *         },
49403  *         renderTo: document.body,
49404  *         items: [{
49405  *             xtype: 'panel',
49406  *             title: 'Inner Panel One',
49407  *             width: 250,
49408  *             flex: 2
49409  *         },
49410  *         {
49411  *             xtype: 'panel',
49412  *             title: 'Inner Panel Two',
49413  *             width: 250,
49414  *             flex: 4
49415  *         },
49416  *         {
49417  *             xtype: 'panel',
49418  *             title: 'Inner Panel Three',
49419  *             width: '50%',
49420  *             flex: 4
49421  *         }]
49422  *     });
49423  */
49424 Ext.define('Ext.layout.container.VBox', {
49425
49426     /* Begin Definitions */
49427
49428     alias: ['layout.vbox'],
49429     extend: 'Ext.layout.container.Box',
49430     alternateClassName: 'Ext.layout.VBoxLayout',
49431
49432     /* End Definitions */
49433
49434     /**
49435      * @cfg {String} align
49436      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
49437      *
49438      * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
49439      * - **center** : child items are aligned horizontally at the **mid-width** of the container
49440      * - **stretch** : child items are stretched horizontally to fill the width of the container
49441      * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
49442      */
49443     align : 'left', // left, center, stretch, strechmax
49444
49445     //@private
49446     alignCenteringString: 'center',
49447
49448     type: 'vbox',
49449
49450     direction: 'vertical',
49451
49452     // When creating an argument list to setSize, use this order
49453     parallelSizeIndex: 1,
49454     perpendicularSizeIndex: 0,
49455
49456     parallelPrefix: 'height',
49457     parallelPrefixCap: 'Height',
49458     parallelLT: 't',
49459     parallelRB: 'b',
49460     parallelBefore: 'top',
49461     parallelBeforeCap: 'Top',
49462     parallelAfter: 'bottom',
49463     parallelPosition: 'y',
49464
49465     perpendicularPrefix: 'width',
49466     perpendicularPrefixCap: 'Width',
49467     perpendicularLT: 'l',
49468     perpendicularRB: 'r',
49469     perpendicularLeftTop: 'left',
49470     perpendicularRightBottom: 'right',
49471     perpendicularPosition: 'x',
49472     configureItem: function(item) {
49473         if (item.flex) {
49474             item.layoutManagedHeight = 1;
49475         } else {
49476             item.layoutManagedHeight = 2;
49477         }
49478
49479         if (this.align === 'stretch' || this.align === 'stretchmax') {
49480             item.layoutManagedWidth = 1;
49481         } else {
49482             item.layoutManagedWidth = 2;
49483         }
49484         this.callParent(arguments);
49485     }
49486 });
49487 /**
49488  * @class Ext.FocusManager
49489
49490 The FocusManager is responsible for globally:
49491
49492 1. Managing component focus
49493 2. Providing basic keyboard navigation
49494 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
49495
49496 To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
49497 deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();.  The
49498 FocusManager is disabled by default.
49499
49500 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
49501
49502 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
49503 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
49504 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
49505 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
49506
49507  * @singleton
49508  * @author Jarred Nicholls <jarred@sencha.com>
49509  * @docauthor Jarred Nicholls <jarred@sencha.com>
49510  */
49511 Ext.define('Ext.FocusManager', {
49512     singleton: true,
49513     alternateClassName: 'Ext.FocusMgr',
49514
49515     mixins: {
49516         observable: 'Ext.util.Observable'
49517     },
49518
49519     requires: [
49520         'Ext.ComponentManager',
49521         'Ext.ComponentQuery',
49522         'Ext.util.HashMap',
49523         'Ext.util.KeyNav'
49524     ],
49525
49526     /**
49527      * @property {Boolean} enabled
49528      * Whether or not the FocusManager is currently enabled
49529      */
49530     enabled: false,
49531
49532     /**
49533      * @property {Ext.Component} focusedCmp
49534      * The currently focused component. Defaults to `undefined`.
49535      */
49536
49537     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
49538
49539     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
49540
49541     /**
49542      * @property {String[]} whitelist
49543      * A list of xtypes that should ignore certain navigation input keys and
49544      * allow for the default browser event/behavior. These input keys include:
49545      *
49546      * 1. Backspace
49547      * 2. Delete
49548      * 3. Left
49549      * 4. Right
49550      * 5. Up
49551      * 6. Down
49552      *
49553      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
49554      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
49555      * the user to move the input cursor left and right, and to delete characters, etc.
49556      */
49557     whitelist: [
49558         'textfield'
49559     ],
49560
49561     tabIndexWhitelist: [
49562         'a',
49563         'button',
49564         'embed',
49565         'frame',
49566         'iframe',
49567         'img',
49568         'input',
49569         'object',
49570         'select',
49571         'textarea'
49572     ],
49573
49574     constructor: function() {
49575         var me = this,
49576             CQ = Ext.ComponentQuery;
49577
49578         me.addEvents(
49579             /**
49580              * @event beforecomponentfocus
49581              * Fires before a component becomes focused. Return `false` to prevent
49582              * the component from gaining focus.
49583              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49584              * @param {Ext.Component} cmp The component that is being focused
49585              * @param {Ext.Component} previousCmp The component that was previously focused,
49586              * or `undefined` if there was no previously focused component.
49587              */
49588             'beforecomponentfocus',
49589
49590             /**
49591              * @event componentfocus
49592              * Fires after a component becomes focused.
49593              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49594              * @param {Ext.Component} cmp The component that has been focused
49595              * @param {Ext.Component} previousCmp The component that was previously focused,
49596              * or `undefined` if there was no previously focused component.
49597              */
49598             'componentfocus',
49599
49600             /**
49601              * @event disable
49602              * Fires when the FocusManager is disabled
49603              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49604              */
49605             'disable',
49606
49607             /**
49608              * @event enable
49609              * Fires when the FocusManager is enabled
49610              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
49611              */
49612             'enable'
49613         );
49614
49615         // Setup KeyNav that's bound to document to catch all
49616         // unhandled/bubbled key events for navigation
49617         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
49618             disabled: true,
49619             scope: me,
49620
49621             backspace: me.focusLast,
49622             enter: me.navigateIn,
49623             esc: me.navigateOut,
49624             tab: me.navigateSiblings
49625
49626             //space: me.navigateIn,
49627             //del: me.focusLast,
49628             //left: me.navigateSiblings,
49629             //right: me.navigateSiblings,
49630             //down: me.navigateSiblings,
49631             //up: me.navigateSiblings
49632         });
49633
49634         me.focusData = {};
49635         me.subscribers = Ext.create('Ext.util.HashMap');
49636         me.focusChain = {};
49637
49638         // Setup some ComponentQuery pseudos
49639         Ext.apply(CQ.pseudos, {
49640             focusable: function(cmps) {
49641                 var len = cmps.length,
49642                     results = [],
49643                     i = 0,
49644                     c,
49645
49646                     isFocusable = function(x) {
49647                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
49648                     };
49649
49650                 for (; i < len; i++) {
49651                     c = cmps[i];
49652                     if (isFocusable(c)) {
49653                         results.push(c);
49654                     }
49655                 }
49656
49657                 return results;
49658             },
49659
49660             nextFocus: function(cmps, idx, step) {
49661                 step = step || 1;
49662                 idx = parseInt(idx, 10);
49663
49664                 var len = cmps.length,
49665                     i = idx + step,
49666                     c;
49667
49668                 for (; i != idx; i += step) {
49669                     if (i >= len) {
49670                         i = 0;
49671                     } else if (i < 0) {
49672                         i = len - 1;
49673                     }
49674
49675                     c = cmps[i];
49676                     if (CQ.is(c, ':focusable')) {
49677                         return [c];
49678                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
49679                         return [c.placeholder];
49680                     }
49681                 }
49682
49683                 return [];
49684             },
49685
49686             prevFocus: function(cmps, idx) {
49687                 return this.nextFocus(cmps, idx, -1);
49688             },
49689
49690             root: function(cmps) {
49691                 var len = cmps.length,
49692                     results = [],
49693                     i = 0,
49694                     c;
49695
49696                 for (; i < len; i++) {
49697                     c = cmps[i];
49698                     if (!c.ownerCt) {
49699                         results.push(c);
49700                     }
49701                 }
49702
49703                 return results;
49704             }
49705         });
49706     },
49707
49708     /**
49709      * Adds the specified xtype to the {@link #whitelist}.
49710      * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
49711      */
49712     addXTypeToWhitelist: function(xtype) {
49713         var me = this;
49714
49715         if (Ext.isArray(xtype)) {
49716             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
49717             return;
49718         }
49719
49720         if (!Ext.Array.contains(me.whitelist, xtype)) {
49721             me.whitelist.push(xtype);
49722         }
49723     },
49724
49725     clearComponent: function(cmp) {
49726         clearTimeout(this.cmpFocusDelay);
49727         if (!cmp.isDestroyed) {
49728             cmp.blur();
49729         }
49730     },
49731
49732     /**
49733      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
49734      */
49735     disable: function() {
49736         var me = this;
49737
49738         if (!me.enabled) {
49739             return;
49740         }
49741
49742         delete me.options;
49743         me.enabled = false;
49744
49745         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
49746
49747         me.removeDOM();
49748
49749         // Stop handling key navigation
49750         me.keyNav.disable();
49751
49752         // disable focus for all components
49753         me.setFocusAll(false);
49754
49755         me.fireEvent('disable', me);
49756     },
49757
49758     /**
49759      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
49760      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
49761         - focusFrame : Boolean
49762             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49763      * @markdown
49764      */
49765     enable: function(options) {
49766         var me = this;
49767
49768         if (options === true) {
49769             options = { focusFrame: true };
49770         }
49771         me.options = options = options || {};
49772
49773         if (me.enabled) {
49774             return;
49775         }
49776
49777         // Handle components that are newly added after we are enabled
49778         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
49779
49780         me.initDOM(options);
49781
49782         // Start handling key navigation
49783         me.keyNav.enable();
49784
49785         // enable focus for all components
49786         me.setFocusAll(true, options);
49787
49788         // Finally, let's focus our global focus el so we start fresh
49789         me.focusEl.focus();
49790         delete me.focusedCmp;
49791
49792         me.enabled = true;
49793         me.fireEvent('enable', me);
49794     },
49795
49796     focusLast: function(e) {
49797         var me = this;
49798
49799         if (me.isWhitelisted(me.focusedCmp)) {
49800             return true;
49801         }
49802
49803         // Go back to last focused item
49804         if (me.previousFocusedCmp) {
49805             me.previousFocusedCmp.focus();
49806         }
49807     },
49808
49809     getRootComponents: function() {
49810         var me = this,
49811             CQ = Ext.ComponentQuery,
49812             inline = CQ.query(':focusable:root:not([floating])'),
49813             floating = CQ.query(':focusable:root[floating]');
49814
49815         // Floating items should go to the top of our root stack, and be ordered
49816         // by their z-index (highest first)
49817         floating.sort(function(a, b) {
49818             return a.el.getZIndex() > b.el.getZIndex();
49819         });
49820
49821         return floating.concat(inline);
49822     },
49823
49824     initDOM: function(options) {
49825         var me = this,
49826             sp = '&#160',
49827             cls = me.focusFrameCls;
49828
49829         if (!Ext.isReady) {
49830             Ext.onReady(me.initDOM, me);
49831             return;
49832         }
49833
49834         // Create global focus element
49835         if (!me.focusEl) {
49836             me.focusEl = Ext.getBody().createChild({
49837                 tabIndex: '-1',
49838                 cls: me.focusElementCls,
49839                 html: sp
49840             });
49841         }
49842
49843         // Create global focus frame
49844         if (!me.focusFrame && options.focusFrame) {
49845             me.focusFrame = Ext.getBody().createChild({
49846                 cls: cls,
49847                 children: [
49848                     { cls: cls + '-top' },
49849                     { cls: cls + '-bottom' },
49850                     { cls: cls + '-left' },
49851                     { cls: cls + '-right' }
49852                 ],
49853                 style: 'top: -100px; left: -100px;'
49854             });
49855             me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
49856             me.focusFrameWidth = 2;
49857             me.focusFrame.hide().setLeftTop(0, 0);
49858         }
49859     },
49860
49861     isWhitelisted: function(cmp) {
49862         return cmp && Ext.Array.some(this.whitelist, function(x) {
49863             return cmp.isXType(x);
49864         });
49865     },
49866
49867     navigateIn: function(e) {
49868         var me = this,
49869             focusedCmp = me.focusedCmp,
49870             rootCmps,
49871             firstChild;
49872
49873         if (!focusedCmp) {
49874             // No focus yet, so focus the first root cmp on the page
49875             rootCmps = me.getRootComponents();
49876             if (rootCmps.length) {
49877                 rootCmps[0].focus();
49878             }
49879         } else {
49880             // Drill into child ref items of the focused cmp, if applicable.
49881             // This works for any Component with a getRefItems implementation.
49882             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
49883             if (firstChild) {
49884                 firstChild.focus();
49885             } else {
49886                 // Let's try to fire a click event, as if it came from the mouse
49887                 if (Ext.isFunction(focusedCmp.onClick)) {
49888                     e.button = 0;
49889                     focusedCmp.onClick(e);
49890                     focusedCmp.focus();
49891                 }
49892             }
49893         }
49894     },
49895
49896     navigateOut: function(e) {
49897         var me = this,
49898             parent;
49899
49900         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
49901             me.focusEl.focus();
49902         } else {
49903             parent.focus();
49904         }
49905
49906         // In some browsers (Chrome) FocusManager can handle this before other
49907         // handlers. Ext Windows have their own Esc key handling, so we need to
49908         // return true here to allow the event to bubble.
49909         return true;
49910     },
49911
49912     navigateSiblings: function(e, source, parent) {
49913         var me = this,
49914             src = source || me,
49915             key = e.getKey(),
49916             EO = Ext.EventObject,
49917             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
49918             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
49919             nextSelector = goBack ? 'prev' : 'next',
49920             idx, next, focusedCmp;
49921
49922         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
49923         if (!focusedCmp && !parent) {
49924             return;
49925         }
49926
49927         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
49928             return true;
49929         }
49930
49931         parent = parent || focusedCmp.up();
49932         if (parent) {
49933             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
49934             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
49935             if (next && focusedCmp !== next) {
49936                 next.focus();
49937                 return next;
49938             }
49939         }
49940     },
49941
49942     onComponentBlur: function(cmp, e) {
49943         var me = this;
49944
49945         if (me.focusedCmp === cmp) {
49946             me.previousFocusedCmp = cmp;
49947             delete me.focusedCmp;
49948         }
49949
49950         if (me.focusFrame) {
49951             me.focusFrame.hide();
49952         }
49953     },
49954
49955     onComponentCreated: function(hash, id, cmp) {
49956         this.setFocus(cmp, true, this.options);
49957     },
49958
49959     onComponentDestroy: function(cmp) {
49960         this.setFocus(cmp, false);
49961     },
49962
49963     onComponentFocus: function(cmp, e) {
49964         var me = this,
49965             chain = me.focusChain;
49966
49967         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
49968             me.clearComponent(cmp);
49969
49970             // Check our focus chain, so we don't run into a never ending recursion
49971             // If we've attempted (unsuccessfully) to focus this component before,
49972             // then we're caught in a loop of child->parent->...->child and we
49973             // need to cut the loop off rather than feed into it.
49974             if (chain[cmp.id]) {
49975                 return;
49976             }
49977
49978             // Try to focus the parent instead
49979             var parent = cmp.up();
49980             if (parent) {
49981                 // Add component to our focus chain to detect infinite focus loop
49982                 // before we fire off an attempt to focus our parent.
49983                 // See the comments above.
49984                 chain[cmp.id] = true;
49985                 parent.focus();
49986             }
49987
49988             return;
49989         }
49990
49991         // Clear our focus chain when we have a focusable component
49992         me.focusChain = {};
49993
49994         // Defer focusing for 90ms so components can do a layout/positioning
49995         // and give us an ability to buffer focuses
49996         clearTimeout(me.cmpFocusDelay);
49997         if (arguments.length !== 2) {
49998             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
49999             return;
50000         }
50001
50002         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
50003             me.clearComponent(cmp);
50004             return;
50005         }
50006
50007         me.focusedCmp = cmp;
50008
50009         // If we have a focus frame, show it around the focused component
50010         if (me.shouldShowFocusFrame(cmp)) {
50011             var cls = '.' + me.focusFrameCls + '-',
50012                 ff = me.focusFrame,
50013                 fw = me.focusFrameWidth,
50014                 box = cmp.el.getPageBox(),
50015
50016             // Size the focus frame's t/b/l/r according to the box
50017             // This leaves a hole in the middle of the frame so user
50018             // interaction w/ the mouse can continue
50019                 bt = box.top,
50020                 bl = box.left,
50021                 bw = box.width,
50022                 bh = box.height,
50023                 ft = ff.child(cls + 'top'),
50024                 fb = ff.child(cls + 'bottom'),
50025                 fl = ff.child(cls + 'left'),
50026                 fr = ff.child(cls + 'right');
50027
50028             ft.setWidth(bw).setLeftTop(bl, bt);
50029             fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
50030             fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
50031             fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
50032
50033             ff.show();
50034         }
50035
50036         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
50037     },
50038
50039     onComponentHide: function(cmp) {
50040         var me = this,
50041             CQ = Ext.ComponentQuery,
50042             cmpHadFocus = false,
50043             focusedCmp,
50044             parent;
50045
50046         if (me.focusedCmp) {
50047             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
50048             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
50049
50050             if (focusedCmp) {
50051                 me.clearComponent(focusedCmp);
50052             }
50053         }
50054
50055         me.clearComponent(cmp);
50056
50057         if (cmpHadFocus) {
50058             parent = CQ.query('^:focusable', cmp)[0];
50059             if (parent) {
50060                 parent.focus();
50061             }
50062         }
50063     },
50064
50065     removeDOM: function() {
50066         var me = this;
50067
50068         // If we are still enabled globally, or there are still subscribers
50069         // then we will halt here, since our DOM stuff is still being used
50070         if (me.enabled || me.subscribers.length) {
50071             return;
50072         }
50073
50074         Ext.destroy(
50075             me.focusEl,
50076             me.focusFrame
50077         );
50078         delete me.focusEl;
50079         delete me.focusFrame;
50080         delete me.focusFrameWidth;
50081     },
50082
50083     /**
50084      * Removes the specified xtype from the {@link #whitelist}.
50085      * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
50086      */
50087     removeXTypeFromWhitelist: function(xtype) {
50088         var me = this;
50089
50090         if (Ext.isArray(xtype)) {
50091             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
50092             return;
50093         }
50094
50095         Ext.Array.remove(me.whitelist, xtype);
50096     },
50097
50098     setFocus: function(cmp, focusable, options) {
50099         var me = this,
50100             el, dom, data,
50101
50102             needsTabIndex = function(n) {
50103                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
50104                     && n.tabIndex <= 0;
50105             };
50106
50107         options = options || {};
50108
50109         // Come back and do this after the component is rendered
50110         if (!cmp.rendered) {
50111             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
50112             return;
50113         }
50114
50115         el = cmp.getFocusEl();
50116         dom = el.dom;
50117
50118         // Decorate the component's focus el for focus-ability
50119         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
50120             if (focusable) {
50121                 data = {
50122                     focusFrame: options.focusFrame
50123                 };
50124
50125                 // Only set -1 tabIndex if we need it
50126                 // inputs, buttons, and anchor tags do not need it,
50127                 // and neither does any DOM that has it set already
50128                 // programmatically or in markup.
50129                 if (needsTabIndex(dom)) {
50130                     data.tabIndex = dom.tabIndex;
50131                     dom.tabIndex = -1;
50132                 }
50133
50134                 el.on({
50135                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
50136                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
50137                     scope: me
50138                 });
50139                 cmp.on({
50140                     hide: me.onComponentHide,
50141                     close: me.onComponentHide,
50142                     beforedestroy: me.onComponentDestroy,
50143                     scope: me
50144                 });
50145
50146                 me.focusData[cmp.id] = data;
50147             } else {
50148                 data = me.focusData[cmp.id];
50149                 if ('tabIndex' in data) {
50150                     dom.tabIndex = data.tabIndex;
50151                 }
50152                 el.un('focus', data.focusFn, me);
50153                 el.un('blur', data.blurFn, me);
50154                 cmp.un('hide', me.onComponentHide, me);
50155                 cmp.un('close', me.onComponentHide, me);
50156                 cmp.un('beforedestroy', me.onComponentDestroy, me);
50157
50158                 delete me.focusData[cmp.id];
50159             }
50160         }
50161     },
50162
50163     setFocusAll: function(focusable, options) {
50164         var me = this,
50165             cmps = Ext.ComponentManager.all.getArray(),
50166             len = cmps.length,
50167             cmp,
50168             i = 0;
50169
50170         for (; i < len; i++) {
50171             me.setFocus(cmps[i], focusable, options);
50172         }
50173     },
50174
50175     setupSubscriberKeys: function(container, keys) {
50176         var me = this,
50177             el = container.getFocusEl(),
50178             scope = keys.scope,
50179             handlers = {
50180                 backspace: me.focusLast,
50181                 enter: me.navigateIn,
50182                 esc: me.navigateOut,
50183                 scope: me
50184             },
50185
50186             navSiblings = function(e) {
50187                 if (me.focusedCmp === container) {
50188                     // Root the sibling navigation to this container, so that we
50189                     // can automatically dive into the container, rather than forcing
50190                     // the user to hit the enter key to dive in.
50191                     return me.navigateSiblings(e, me, container);
50192                 } else {
50193                     return me.navigateSiblings(e);
50194                 }
50195             };
50196
50197         Ext.iterate(keys, function(key, cb) {
50198             handlers[key] = function(e) {
50199                 var ret = navSiblings(e);
50200
50201                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
50202                     return true;
50203                 }
50204
50205                 return ret;
50206             };
50207         }, me);
50208
50209         return Ext.create('Ext.util.KeyNav', el, handlers);
50210     },
50211
50212     shouldShowFocusFrame: function(cmp) {
50213         var me = this,
50214             opts = me.options || {};
50215
50216         if (!me.focusFrame || !cmp) {
50217             return false;
50218         }
50219
50220         // Global trumps
50221         if (opts.focusFrame) {
50222             return true;
50223         }
50224
50225         if (me.focusData[cmp.id].focusFrame) {
50226             return true;
50227         }
50228
50229         return false;
50230     },
50231
50232     /**
50233      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
50234      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
50235      * @param {Boolean/Object} options An object of the following options
50236      * @param {Array/Object} options.keys
50237      * An array containing the string names of navigation keys to be supported. The allowed values are:
50238      *
50239      *   - 'left'
50240      *   - 'right'
50241      *   - 'up'
50242      *   - 'down'
50243      *
50244      * 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.:
50245      *
50246      *     {
50247      *         left: this.onLeftKey,
50248      *         right: this.onRightKey,
50249      *         scope: this
50250      *     }
50251      *
50252      * @param {Boolean} options.focusFrame
50253      * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
50254      */
50255     subscribe: function(container, options) {
50256         var me = this,
50257             EA = Ext.Array,
50258             data = {},
50259             subs = me.subscribers,
50260
50261             // Recursively add focus ability as long as a descendent container isn't
50262             // itself subscribed to the FocusManager, or else we'd have unwanted side
50263             // effects for subscribing a descendent container twice.
50264             safeSetFocus = function(cmp) {
50265                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
50266                     EA.forEach(cmp.query('>'), safeSetFocus);
50267                     me.setFocus(cmp, true, options);
50268                     cmp.on('add', data.onAdd, me);
50269                 } else if (!cmp.isContainer) {
50270                     me.setFocus(cmp, true, options);
50271                 }
50272             };
50273
50274         // We only accept containers
50275         if (!container || !container.isContainer) {
50276             return;
50277         }
50278
50279         if (!container.rendered) {
50280             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
50281             return;
50282         }
50283
50284         // Init the DOM, incase this is the first time it will be used
50285         me.initDOM(options);
50286
50287         // Create key navigation for subscriber based on keys option
50288         data.keyNav = me.setupSubscriberKeys(container, options.keys);
50289
50290         // We need to keep track of components being added to our subscriber
50291         // and any containers nested deeply within it (omg), so let's do that.
50292         // Components that are removed are globally handled.
50293         // Also keep track of destruction of our container for auto-unsubscribe.
50294         data.onAdd = function(ct, cmp, idx) {
50295             safeSetFocus(cmp);
50296         };
50297         container.on('beforedestroy', me.unsubscribe, me);
50298
50299         // Now we setup focusing abilities for the container and all its components
50300         safeSetFocus(container);
50301
50302         // Add to our subscribers list
50303         subs.add(container.id, data);
50304     },
50305
50306     /**
50307      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
50308      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
50309      */
50310     unsubscribe: function(container) {
50311         var me = this,
50312             EA = Ext.Array,
50313             subs = me.subscribers,
50314             data,
50315
50316             // Recursively remove focus ability as long as a descendent container isn't
50317             // itself subscribed to the FocusManager, or else we'd have unwanted side
50318             // effects for unsubscribing an ancestor container.
50319             safeSetFocus = function(cmp) {
50320                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
50321                     EA.forEach(cmp.query('>'), safeSetFocus);
50322                     me.setFocus(cmp, false);
50323                     cmp.un('add', data.onAdd, me);
50324                 } else if (!cmp.isContainer) {
50325                     me.setFocus(cmp, false);
50326                 }
50327             };
50328
50329         if (!container || !subs.containsKey(container.id)) {
50330             return;
50331         }
50332
50333         data = subs.get(container.id);
50334         data.keyNav.destroy();
50335         container.un('beforedestroy', me.unsubscribe, me);
50336         subs.removeAtKey(container.id);
50337         safeSetFocus(container);
50338         me.removeDOM();
50339     }
50340 });
50341 /**
50342  * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
50343  * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
50344  * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
50345  *
50346  * ## Some items have shortcut strings for creation:
50347  *
50348  * | Shortcut | xtype         | Class                         | Description
50349  * |:---------|:--------------|:------------------------------|:---------------------------------------------------
50350  * | `->`     | `tbfill`      | {@link Ext.toolbar.Fill}      | begin using the right-justified button container
50351  * | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
50352  * | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements
50353  *
50354  *     @example
50355  *     Ext.create('Ext.toolbar.Toolbar', {
50356  *         renderTo: document.body,
50357  *         width   : 500,
50358  *         items: [
50359  *             {
50360  *                 // xtype: 'button', // default for Toolbars
50361  *                 text: 'Button'
50362  *             },
50363  *             {
50364  *                 xtype: 'splitbutton',
50365  *                 text : 'Split Button'
50366  *             },
50367  *             // begin using the right-justified button container
50368  *             '->', // same as { xtype: 'tbfill' }
50369  *             {
50370  *                 xtype    : 'textfield',
50371  *                 name     : 'field1',
50372  *                 emptyText: 'enter search term'
50373  *             },
50374  *             // add a vertical separator bar between toolbar items
50375  *             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
50376  *             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
50377  *             { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
50378  *             'text 2',
50379  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
50380  *             'text 3'
50381  *         ]
50382  *     });
50383  *
50384  * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
50385  *
50386  *     @example
50387  *     Ext.create('Ext.toolbar.Toolbar', {
50388  *         renderTo: document.body,
50389  *         width   : 400,
50390  *         items: [
50391  *             {
50392  *                 text: 'Button'
50393  *             },
50394  *             {
50395  *                 xtype: 'splitbutton',
50396  *                 text : 'Split Button'
50397  *             },
50398  *             '->',
50399  *             {
50400  *                 xtype    : 'textfield',
50401  *                 name     : 'field1',
50402  *                 emptyText: 'enter search term'
50403  *             }
50404  *         ]
50405  *     });
50406  *
50407  * Example
50408  *
50409  *     @example
50410  *     var enableBtn = Ext.create('Ext.button.Button', {
50411  *         text    : 'Enable All Items',
50412  *         disabled: true,
50413  *         scope   : this,
50414  *         handler : function() {
50415  *             //disable the enable button and enable the disable button
50416  *             enableBtn.disable();
50417  *             disableBtn.enable();
50418  *
50419  *             //enable the toolbar
50420  *             toolbar.enable();
50421  *         }
50422  *     });
50423  *
50424  *     var disableBtn = Ext.create('Ext.button.Button', {
50425  *         text    : 'Disable All Items',
50426  *         scope   : this,
50427  *         handler : function() {
50428  *             //enable the enable button and disable button
50429  *             disableBtn.disable();
50430  *             enableBtn.enable();
50431  *
50432  *             //disable the toolbar
50433  *             toolbar.disable();
50434  *         }
50435  *     });
50436  *
50437  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
50438  *         renderTo: document.body,
50439  *         width   : 400,
50440  *         margin  : '5 0 0 0',
50441  *         items   : [enableBtn, disableBtn]
50442  *     });
50443  *
50444  * 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
50445  * which remove all items within the toolbar.
50446  *
50447  *     @example
50448  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
50449  *         renderTo: document.body,
50450  *         width   : 700,
50451  *         items: [
50452  *             {
50453  *                 text: 'Example Button'
50454  *             }
50455  *         ]
50456  *     });
50457  *
50458  *     var addedItems = [];
50459  *
50460  *     Ext.create('Ext.toolbar.Toolbar', {
50461  *         renderTo: document.body,
50462  *         width   : 700,
50463  *         margin  : '5 0 0 0',
50464  *         items   : [
50465  *             {
50466  *                 text   : 'Add a button',
50467  *                 scope  : this,
50468  *                 handler: function() {
50469  *                     var text = prompt('Please enter the text for your button:');
50470  *                     addedItems.push(toolbar.add({
50471  *                         text: text
50472  *                     }));
50473  *                 }
50474  *             },
50475  *             {
50476  *                 text   : 'Add a text item',
50477  *                 scope  : this,
50478  *                 handler: function() {
50479  *                     var text = prompt('Please enter the text for your item:');
50480  *                     addedItems.push(toolbar.add(text));
50481  *                 }
50482  *             },
50483  *             {
50484  *                 text   : 'Add a toolbar seperator',
50485  *                 scope  : this,
50486  *                 handler: function() {
50487  *                     addedItems.push(toolbar.add('-'));
50488  *                 }
50489  *             },
50490  *             {
50491  *                 text   : 'Add a toolbar spacer',
50492  *                 scope  : this,
50493  *                 handler: function() {
50494  *                     addedItems.push(toolbar.add('->'));
50495  *                 }
50496  *             },
50497  *             '->',
50498  *             {
50499  *                 text   : 'Remove last inserted item',
50500  *                 scope  : this,
50501  *                 handler: function() {
50502  *                     if (addedItems.length) {
50503  *                         toolbar.remove(addedItems.pop());
50504  *                     } else if (toolbar.items.length) {
50505  *                         toolbar.remove(toolbar.items.last());
50506  *                     } else {
50507  *                         alert('No items in the toolbar');
50508  *                     }
50509  *                 }
50510  *             },
50511  *             {
50512  *                 text   : 'Remove all items',
50513  *                 scope  : this,
50514  *                 handler: function() {
50515  *                     toolbar.removeAll();
50516  *                 }
50517  *             }
50518  *         ]
50519  *     });
50520  *
50521  * @constructor
50522  * Creates a new Toolbar
50523  * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
50524  * @docauthor Robert Dougan <rob@sencha.com>
50525  */
50526 Ext.define('Ext.toolbar.Toolbar', {
50527     extend: 'Ext.container.Container',
50528     requires: [
50529         'Ext.toolbar.Fill',
50530         'Ext.layout.container.HBox',
50531         'Ext.layout.container.VBox',
50532         'Ext.FocusManager'
50533     ],
50534     uses: [
50535         'Ext.toolbar.Separator'
50536     ],
50537     alias: 'widget.toolbar',
50538     alternateClassName: 'Ext.Toolbar',
50539
50540     isToolbar: true,
50541     baseCls  : Ext.baseCSSPrefix + 'toolbar',
50542     ariaRole : 'toolbar',
50543
50544     defaultType: 'button',
50545
50546     /**
50547      * @cfg {Boolean} vertical
50548      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
50549      */
50550     vertical: false,
50551
50552     /**
50553      * @cfg {String/Object} layout
50554      * This class assigns a default layout (`layout: 'hbox'`).
50555      * Developers _may_ override this configuration option if another layout
50556      * is required (the constructor must be passed a configuration object in this
50557      * case instead of an array).
50558      * See {@link Ext.container.Container#layout} for additional information.
50559      */
50560
50561     /**
50562      * @cfg {Boolean} enableOverflow
50563      * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
50564      * items which overflow the Toolbar's width.
50565      */
50566     enableOverflow: false,
50567
50568     /**
50569      * @cfg {String} menuTriggerCls
50570      * Configure the icon class of the overflow button.
50571      */
50572     menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
50573     
50574     // private
50575     trackMenus: true,
50576
50577     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
50578
50579     initComponent: function() {
50580         var me = this,
50581             keys;
50582
50583         // check for simplified (old-style) overflow config:
50584         if (!me.layout && me.enableOverflow) {
50585             me.layout = { overflowHandler: 'Menu' };
50586         }
50587
50588         if (me.dock === 'right' || me.dock === 'left') {
50589             me.vertical = true;
50590         }
50591
50592         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
50593             type: me.layout
50594         } : me.layout || {}, {
50595             type: me.vertical ? 'vbox' : 'hbox',
50596             align: me.vertical ? 'stretchmax' : 'middle',
50597             clearInnerCtOnLayout: true
50598         });
50599
50600         if (me.vertical) {
50601             me.addClsWithUI('vertical');
50602         }
50603
50604         // @TODO: remove this hack and implement a more general solution
50605         if (me.ui === 'footer') {
50606             me.ignoreBorderManagement = true;
50607         }
50608
50609         me.callParent();
50610
50611         /**
50612          * @event overflowchange
50613          * Fires after the overflow state has changed.
50614          * @param {Object} c The Container
50615          * @param {Boolean} lastOverflow overflow state
50616          */
50617         me.addEvents('overflowchange');
50618
50619         // Subscribe to Ext.FocusManager for key navigation
50620         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
50621         Ext.FocusManager.subscribe(me, {
50622             keys: keys
50623         });
50624     },
50625
50626     getRefItems: function(deep) {
50627         var me = this,
50628             items = me.callParent(arguments),
50629             layout = me.layout,
50630             handler;
50631
50632         if (deep && me.enableOverflow) {
50633             handler = layout.overflowHandler;
50634             if (handler && handler.menu) {
50635                 items = items.concat(handler.menu.getRefItems(deep));
50636             }
50637         }
50638         return items;
50639     },
50640
50641     /**
50642      * Adds element(s) to the toolbar -- this function takes a variable number of
50643      * arguments of mixed type and adds them to the toolbar.
50644      *
50645      * **Note**: See the notes within {@link Ext.container.Container#add}.
50646      *
50647      * @param {Object...} args The following types of arguments are all valid:
50648      *  - `{@link Ext.button.Button config}`: A valid button config object
50649      *  - `HtmlElement`: Any standard HTML element
50650      *  - `Field`: Any form field
50651      *  - `Item`: Any subclass of {@link Ext.toolbar.Item}
50652      *  - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
50653      *  Note that there are a few special strings that are treated differently as explained next.
50654      *  - `'-'`: Creates a separator element
50655      *  - `' '`: Creates a spacer element
50656      *  - `'->'`: Creates a fill element
50657      *
50658      * @method add
50659      */
50660
50661     // private
50662     lookupComponent: function(c) {
50663         if (Ext.isString(c)) {
50664             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
50665             if (shortcut) {
50666                 c = {
50667                     xtype: shortcut
50668                 };
50669             } else {
50670                 c = {
50671                     xtype: 'tbtext',
50672                     text: c
50673                 };
50674             }
50675             this.applyDefaults(c);
50676         }
50677         return this.callParent(arguments);
50678     },
50679
50680     // private
50681     applyDefaults: function(c) {
50682         if (!Ext.isString(c)) {
50683             c = this.callParent(arguments);
50684             var d = this.internalDefaults;
50685             if (c.events) {
50686                 Ext.applyIf(c.initialConfig, d);
50687                 Ext.apply(c, d);
50688             } else {
50689                 Ext.applyIf(c, d);
50690             }
50691         }
50692         return c;
50693     },
50694
50695     // private
50696     trackMenu: function(item, remove) {
50697         if (this.trackMenus && item.menu) {
50698             var method = remove ? 'mun' : 'mon',
50699                 me = this;
50700
50701             me[method](item, 'mouseover', me.onButtonOver, me);
50702             me[method](item, 'menushow', me.onButtonMenuShow, me);
50703             me[method](item, 'menuhide', me.onButtonMenuHide, me);
50704         }
50705     },
50706
50707     // private
50708     constructButton: function(item) {
50709         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
50710     },
50711
50712     // private
50713     onBeforeAdd: function(component) {
50714         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
50715             component.ui = component.ui + '-toolbar';
50716         }
50717
50718         // Any separators needs to know if is vertical or not
50719         if (component instanceof Ext.toolbar.Separator) {
50720             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
50721         }
50722
50723         this.callParent(arguments);
50724     },
50725
50726     // private
50727     onAdd: function(component) {
50728         this.callParent(arguments);
50729
50730         this.trackMenu(component);
50731         if (this.disabled) {
50732             component.disable();
50733         }
50734     },
50735
50736     // private
50737     onRemove: function(c) {
50738         this.callParent(arguments);
50739         this.trackMenu(c, true);
50740     },
50741
50742     // private
50743     onButtonOver: function(btn){
50744         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
50745             this.activeMenuBtn.hideMenu();
50746             btn.showMenu();
50747             this.activeMenuBtn = btn;
50748         }
50749     },
50750
50751     // private
50752     onButtonMenuShow: function(btn) {
50753         this.activeMenuBtn = btn;
50754     },
50755
50756     // private
50757     onButtonMenuHide: function(btn) {
50758         delete this.activeMenuBtn;
50759     }
50760 }, function() {
50761     this.shortcuts = {
50762         '-' : 'tbseparator',
50763         ' ' : 'tbspacer',
50764         '->': 'tbfill'
50765     };
50766 });
50767 /**
50768  * @class Ext.panel.AbstractPanel
50769  * @extends Ext.container.Container
50770  * A base class which provides methods common to Panel classes across the Sencha product range.
50771  * @private
50772  */
50773 Ext.define('Ext.panel.AbstractPanel', {
50774
50775     /* Begin Definitions */
50776
50777     extend: 'Ext.container.Container',
50778
50779     requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
50780
50781     /* End Definitions */
50782
50783     /**
50784      * @cfg {String} [baseCls='x-panel']
50785      * The base CSS class to apply to this panel's element.
50786      */
50787     baseCls : Ext.baseCSSPrefix + 'panel',
50788
50789     /**
50790      * @cfg {Number/String} bodyPadding
50791      * A shortcut for setting a padding style on the body element. The value can either be
50792      * a number to be applied to all sides, or a normal css string describing padding.
50793      */
50794
50795     /**
50796      * @cfg {Boolean} bodyBorder
50797      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
50798      * which has the {@link #frame} configuration set to `true`.
50799      */
50800
50801     /**
50802      * @cfg {String/Object/Function} bodyStyle
50803      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
50804      * an object containing style property name/value pairs or a function that returns such a string or object.
50805      * For example, these two formats are interpreted to be equivalent:<pre><code>
50806 bodyStyle: 'background:#ffc; padding:10px;'
50807
50808 bodyStyle: {
50809     background: '#ffc',
50810     padding: '10px'
50811 }
50812      * </code></pre>
50813      */
50814
50815     /**
50816      * @cfg {String/String[]} bodyCls
50817      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
50818      * The following examples are all valid:<pre><code>
50819 bodyCls: 'foo'
50820 bodyCls: 'foo bar'
50821 bodyCls: ['foo', 'bar']
50822      * </code></pre>
50823      */
50824
50825     isPanel: true,
50826
50827     componentLayout: 'dock',
50828
50829     /**
50830      * @cfg {Object} defaultDockWeights
50831      * This object holds the default weights applied to dockedItems that have no weight. These start with a
50832      * weight of 1, to allow negative weights to insert before top items and are odd numbers
50833      * so that even weights can be used to get between different dock orders.
50834      *
50835      * To make default docking order match border layout, do this:
50836      * <pre><code>
50837 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
50838      * Changing these defaults as above or individually on this object will effect all Panels.
50839      * To change the defaults on a single panel, you should replace the entire object:
50840      * <pre><code>
50841 initComponent: function () {
50842     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50843     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
50844
50845     this.callParent();
50846 }</code></pre>
50847      *
50848      * To change only one of the default values, you do this:
50849      * <pre><code>
50850 initComponent: function () {
50851     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50852     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
50853
50854     this.callParent();
50855 }</code></pre>
50856      */
50857     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
50858
50859     renderTpl: [
50860         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50861             ' {baseCls}-body-{ui}<tpl if="uiCls">',
50862                 '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50863             '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
50864         '</div>'
50865     ],
50866
50867     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
50868     /**
50869      * @cfg {Object/Object[]} dockedItems
50870      * A component or series of components to be added as docked items to this panel.
50871      * The docked items can be docked to either the top, right, left or bottom of a panel.
50872      * This is typically used for things like toolbars or tab bars:
50873      * <pre><code>
50874 var panel = new Ext.panel.Panel({
50875     fullscreen: true,
50876     dockedItems: [{
50877         xtype: 'toolbar',
50878         dock: 'top',
50879         items: [{
50880             text: 'Docked to the top'
50881         }]
50882     }]
50883 });</code></pre>
50884      */
50885
50886     border: true,
50887
50888     initComponent : function() {
50889         var me = this;
50890
50891         me.addEvents(
50892             /**
50893              * @event bodyresize
50894              * Fires after the Panel has been resized.
50895              * @param {Ext.panel.Panel} p the Panel which has been resized.
50896              * @param {Number} width The Panel body's new width.
50897              * @param {Number} height The Panel body's new height.
50898              */
50899             'bodyresize'
50900             // // inherited
50901             // 'activate',
50902             // // inherited
50903             // 'deactivate'
50904         );
50905
50906         me.addChildEls('body');
50907
50908         //!frame
50909         //!border
50910
50911         if (me.frame && me.border && me.bodyBorder === undefined) {
50912             me.bodyBorder = false;
50913         }
50914         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
50915             me.manageBodyBorders = true;
50916         }
50917
50918         me.callParent();
50919     },
50920
50921     // @private
50922     initItems : function() {
50923         var me = this,
50924             items = me.dockedItems;
50925
50926         me.callParent();
50927         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
50928         if (items) {
50929             me.addDocked(items);
50930         }
50931     },
50932
50933     /**
50934      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
50935      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
50936      * @return {Ext.Component} The docked component (if found)
50937      */
50938     getDockedComponent: function(comp) {
50939         if (Ext.isObject(comp)) {
50940             comp = comp.getItemId();
50941         }
50942         return this.dockedItems.get(comp);
50943     },
50944
50945     /**
50946      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
50947      * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
50948      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
50949      * @param {String/Number} comp The component id, itemId or position to find
50950      * @return {Ext.Component} The component (if found)
50951      */
50952     getComponent: function(comp) {
50953         var component = this.callParent(arguments);
50954         if (component === undefined && !Ext.isNumber(comp)) {
50955             // If the arg is a numeric index skip docked items
50956             component = this.getDockedComponent(comp);
50957         }
50958         return component;
50959     },
50960
50961     /**
50962      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
50963      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
50964      * @return {String} A CSS style string with body styles, padding and border.
50965      * @private
50966      */
50967     initBodyStyles: function() {
50968         var me = this,
50969             bodyStyle = me.bodyStyle,
50970             styles = [],
50971             Element = Ext.Element,
50972             prop;
50973
50974         if (Ext.isFunction(bodyStyle)) {
50975             bodyStyle = bodyStyle();
50976         }
50977         if (Ext.isString(bodyStyle)) {
50978             styles = bodyStyle.split(';');
50979         } else {
50980             for (prop in bodyStyle) {
50981                 if (bodyStyle.hasOwnProperty(prop)) {
50982                     styles.push(prop + ':' + bodyStyle[prop]);
50983                 }
50984             }
50985         }
50986
50987         if (me.bodyPadding !== undefined) {
50988             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
50989         }
50990         if (me.frame && me.bodyBorder) {
50991             if (!Ext.isNumber(me.bodyBorder)) {
50992                 me.bodyBorder = 1;
50993             }
50994             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
50995         }
50996         delete me.bodyStyle;
50997         return styles.length ? styles.join(';') : undefined;
50998     },
50999
51000     /**
51001      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
51002      * CSS classes to be applied to the body element.
51003      * @return {String} The CSS class(es)
51004      * @private
51005      */
51006     initBodyCls: function() {
51007         var me = this,
51008             cls = '',
51009             bodyCls = me.bodyCls;
51010
51011         if (bodyCls) {
51012             Ext.each(bodyCls, function(v) {
51013                 cls += " " + v;
51014             });
51015             delete me.bodyCls;
51016         }
51017         return cls.length > 0 ? cls : undefined;
51018     },
51019
51020     /**
51021      * Initialized the renderData to be used when rendering the renderTpl.
51022      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
51023      * @private
51024      */
51025     initRenderData: function() {
51026         return Ext.applyIf(this.callParent(), {
51027             bodyStyle: this.initBodyStyles(),
51028             bodyCls: this.initBodyCls()
51029         });
51030     },
51031
51032     /**
51033      * Adds docked item(s) to the panel.
51034      * @param {Object/Object[]} component The Component or array of components to add. The components
51035      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
51036      * 'bottom', 'left').
51037      * @param {Number} pos (optional) The index at which the Component will be added
51038      */
51039     addDocked : function(items, pos) {
51040         var me = this,
51041             i = 0,
51042             item, length;
51043
51044         items = me.prepareItems(items);
51045         length = items.length;
51046
51047         for (; i < length; i++) {
51048             item = items[i];
51049             item.dock = item.dock || 'top';
51050
51051             // Allow older browsers to target docked items to style without borders
51052             if (me.border === false) {
51053                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
51054             }
51055
51056             if (pos !== undefined) {
51057                 me.dockedItems.insert(pos + i, item);
51058             }
51059             else {
51060                 me.dockedItems.add(item);
51061             }
51062             item.onAdded(me, i);
51063             me.onDockedAdd(item);
51064         }
51065
51066         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
51067         me.componentLayout.childrenChanged = true;
51068         if (me.rendered && !me.suspendLayout) {
51069             me.doComponentLayout();
51070         }
51071         return items;
51072     },
51073
51074     // Placeholder empty functions
51075     onDockedAdd : Ext.emptyFn,
51076     onDockedRemove : Ext.emptyFn,
51077
51078     /**
51079      * Inserts docked item(s) to the panel at the indicated position.
51080      * @param {Number} pos The index at which the Component will be inserted
51081      * @param {Object/Object[]} component. The Component or array of components to add. The components
51082      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
51083      * 'bottom', 'left').
51084      */
51085     insertDocked : function(pos, items) {
51086         this.addDocked(items, pos);
51087     },
51088
51089     /**
51090      * Removes the docked item from the panel.
51091      * @param {Ext.Component} item. The Component to remove.
51092      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
51093      */
51094     removeDocked : function(item, autoDestroy) {
51095         var me = this,
51096             layout,
51097             hasLayout;
51098
51099         if (!me.dockedItems.contains(item)) {
51100             return item;
51101         }
51102
51103         layout = me.componentLayout;
51104         hasLayout = layout && me.rendered;
51105
51106         if (hasLayout) {
51107             layout.onRemove(item);
51108         }
51109
51110         me.dockedItems.remove(item);
51111         item.onRemoved();
51112         me.onDockedRemove(item);
51113
51114         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
51115             item.destroy();
51116         } else if (hasLayout) {
51117             // not destroying, make any layout related removals
51118             layout.afterRemove(item);    
51119         }
51120
51121
51122         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
51123         me.componentLayout.childrenChanged = true;
51124         if (!me.destroying && !me.suspendLayout) {
51125             me.doComponentLayout();
51126         }
51127
51128         return item;
51129     },
51130
51131     /**
51132      * Retrieve an array of all currently docked Components.
51133      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
51134      * @return {Ext.Component[]} An array of components.
51135      */
51136     getDockedItems : function(cqSelector) {
51137         var me = this,
51138             defaultWeight = me.defaultDockWeights,
51139             dockedItems;
51140
51141         if (me.dockedItems && me.dockedItems.items.length) {
51142             // Allow filtering of returned docked items by CQ selector.
51143             if (cqSelector) {
51144                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
51145             } else {
51146                 dockedItems = me.dockedItems.items.slice();
51147             }
51148
51149             Ext.Array.sort(dockedItems, function(a, b) {
51150                 // Docked items are ordered by their visual representation by default (t,l,r,b)
51151                 var aw = a.weight || defaultWeight[a.dock],
51152                     bw = b.weight || defaultWeight[b.dock];
51153                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
51154                     return aw - bw;
51155                 }
51156                 return 0;
51157             });
51158
51159             return dockedItems;
51160         }
51161         return [];
51162     },
51163
51164     // inherit docs
51165     addUIClsToElement: function(cls, force) {
51166         var me = this,
51167             result = me.callParent(arguments),
51168             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51169             array, i;
51170
51171         if (!force && me.rendered) {
51172             if (me.bodyCls) {
51173                 me.body.addCls(me.bodyCls);
51174             } else {
51175                 me.body.addCls(classes);
51176             }
51177         } else {
51178             if (me.bodyCls) {
51179                 array = me.bodyCls.split(' ');
51180
51181                 for (i = 0; i < classes.length; i++) {
51182                     if (!Ext.Array.contains(array, classes[i])) {
51183                         array.push(classes[i]);
51184                     }
51185                 }
51186
51187                 me.bodyCls = array.join(' ');
51188             } else {
51189                 me.bodyCls = classes.join(' ');
51190             }
51191         }
51192
51193         return result;
51194     },
51195
51196     // inherit docs
51197     removeUIClsFromElement: function(cls, force) {
51198         var me = this,
51199             result = me.callParent(arguments),
51200             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51201             array, i;
51202
51203         if (!force && me.rendered) {
51204             if (me.bodyCls) {
51205                 me.body.removeCls(me.bodyCls);
51206             } else {
51207                 me.body.removeCls(classes);
51208             }
51209         } else {
51210             if (me.bodyCls) {
51211                 array = me.bodyCls.split(' ');
51212
51213                 for (i = 0; i < classes.length; i++) {
51214                     Ext.Array.remove(array, classes[i]);
51215                 }
51216
51217                 me.bodyCls = array.join(' ');
51218             }
51219         }
51220
51221         return result;
51222     },
51223
51224     // inherit docs
51225     addUIToElement: function(force) {
51226         var me = this,
51227             cls = me.baseCls + '-body-' + me.ui,
51228             array;
51229
51230         me.callParent(arguments);
51231
51232         if (!force && me.rendered) {
51233             if (me.bodyCls) {
51234                 me.body.addCls(me.bodyCls);
51235             } else {
51236                 me.body.addCls(cls);
51237             }
51238         } else {
51239             if (me.bodyCls) {
51240                 array = me.bodyCls.split(' ');
51241
51242                 if (!Ext.Array.contains(array, cls)) {
51243                     array.push(cls);
51244                 }
51245
51246                 me.bodyCls = array.join(' ');
51247             } else {
51248                 me.bodyCls = cls;
51249             }
51250         }
51251     },
51252
51253     // inherit docs
51254     removeUIFromElement: function() {
51255         var me = this,
51256             cls = me.baseCls + '-body-' + me.ui,
51257             array;
51258
51259         me.callParent(arguments);
51260
51261         if (me.rendered) {
51262             if (me.bodyCls) {
51263                 me.body.removeCls(me.bodyCls);
51264             } else {
51265                 me.body.removeCls(cls);
51266             }
51267         } else {
51268             if (me.bodyCls) {
51269                 array = me.bodyCls.split(' ');
51270                 Ext.Array.remove(array, cls);
51271                 me.bodyCls = array.join(' ');
51272             } else {
51273                 me.bodyCls = cls;
51274             }
51275         }
51276     },
51277
51278     // @private
51279     getTargetEl : function() {
51280         return this.body;
51281     },
51282
51283     getRefItems: function(deep) {
51284         var items = this.callParent(arguments),
51285             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
51286             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
51287             ln = dockedItems.length,
51288             i = 0,
51289             item;
51290
51291         // Find the index where we go from top/left docked items to right/bottom docked items
51292         for (; i < ln; i++) {
51293             item = dockedItems[i];
51294             if (item.dock === 'right' || item.dock === 'bottom') {
51295                 break;
51296             }
51297         }
51298
51299         // Return docked items in the top/left position before our container items, and
51300         // return right/bottom positioned items after our container items.
51301         // See AbstractDock.renderItems() for more information.
51302         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
51303     },
51304
51305     beforeDestroy: function(){
51306         var docked = this.dockedItems,
51307             c;
51308
51309         if (docked) {
51310             while ((c = docked.first())) {
51311                 this.removeDocked(c, true);
51312             }
51313         }
51314         this.callParent();
51315     },
51316
51317     setBorder: function(border) {
51318         var me = this;
51319         me.border = (border !== undefined) ? border : true;
51320         if (me.rendered) {
51321             me.doComponentLayout();
51322         }
51323     }
51324 });
51325 /**
51326  * @class Ext.panel.Header
51327  * @extends Ext.container.Container
51328  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
51329  */
51330 Ext.define('Ext.panel.Header', {
51331     extend: 'Ext.container.Container',
51332     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
51333     alias: 'widget.header',
51334
51335     isHeader       : true,
51336     defaultType    : 'tool',
51337     indicateDrag   : false,
51338     weight         : -1,
51339
51340     renderTpl: [
51341         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
51342         '<tpl if="uiCls">',
51343             '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
51344         '</tpl>"',
51345         '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
51346
51347     /**
51348      * @cfg {String} title
51349      * The title text to display
51350      */
51351
51352     /**
51353      * @cfg {String} iconCls
51354      * CSS class for icon in header. Used for displaying an icon to the left of a title.
51355      */
51356
51357     initComponent: function() {
51358         var me = this,
51359             ruleStyle,
51360             rule,
51361             style,
51362             titleTextEl,
51363             ui;
51364
51365         me.indicateDragCls = me.baseCls + '-draggable';
51366         me.title = me.title || '&#160;';
51367         me.tools = me.tools || [];
51368         me.items = me.items || [];
51369         me.orientation = me.orientation || 'horizontal';
51370         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
51371
51372         //add the dock as a ui
51373         //this is so we support top/right/left/bottom headers
51374         me.addClsWithUI(me.orientation);
51375         me.addClsWithUI(me.dock);
51376
51377         me.addChildEls('body');
51378
51379         // Add Icon
51380         if (!Ext.isEmpty(me.iconCls)) {
51381             me.initIconCmp();
51382             me.items.push(me.iconCmp);
51383         }
51384
51385         // Add Title
51386         if (me.orientation == 'vertical') {
51387             // Hack for IE6/7's inability to display an inline-block
51388             if (Ext.isIE6 || Ext.isIE7) {
51389                 me.width = this.width || 24;
51390             } else if (Ext.isIEQuirks) {
51391                 me.width = this.width || 25;
51392             }
51393
51394             me.layout = {
51395                 type : 'vbox',
51396                 align: 'center',
51397                 clearInnerCtOnLayout: true,
51398                 bindToOwnerCtContainer: false
51399             };
51400             me.textConfig = {
51401                 cls: me.baseCls + '-text',
51402                 type: 'text',
51403                 text: me.title,
51404                 rotate: {
51405                     degrees: 90
51406                 }
51407             };
51408             ui = me.ui;
51409             if (Ext.isArray(ui)) {
51410                 ui = ui[0];
51411             }
51412             ruleStyle = '.' + me.baseCls + '-text-' + ui;
51413             if (Ext.scopeResetCSS) {
51414                 ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
51415             }
51416             rule = Ext.util.CSS.getRule(ruleStyle);
51417             if (rule) {
51418                 style = rule.style;
51419             }
51420             if (style) {
51421                 Ext.apply(me.textConfig, {
51422                     'font-family': style.fontFamily,
51423                     'font-weight': style.fontWeight,
51424                     'font-size': style.fontSize,
51425                     fill: style.color
51426                 });
51427             }
51428             me.titleCmp = Ext.create('Ext.draw.Component', {
51429                 ariaRole  : 'heading',
51430                 focusable: false,
51431                 viewBox: false,
51432                 flex : 1,
51433                 autoSize: true,
51434                 margins: '5 0 0 0',
51435                 items: [ me.textConfig ],
51436                 // this is a bit of a cheat: we are not selecting an element of titleCmp
51437                 // but rather of titleCmp.items[0] (so we cannot use childEls)
51438                 renderSelectors: {
51439                     textEl: '.' + me.baseCls + '-text'
51440                 }
51441             });
51442         } else {
51443             me.layout = {
51444                 type : 'hbox',
51445                 align: 'middle',
51446                 clearInnerCtOnLayout: true,
51447                 bindToOwnerCtContainer: false
51448             };
51449             me.titleCmp = Ext.create('Ext.Component', {
51450                 xtype     : 'component',
51451                 ariaRole  : 'heading',
51452                 focusable: false,
51453                 flex : 1,
51454                 cls: me.baseCls + '-text-container',
51455                 renderTpl : [
51456                     '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>'
51457                 ],
51458                 renderData: {
51459                     title: me.title,
51460                     cls  : me.baseCls,
51461                     ui   : me.ui
51462                 },
51463                 childEls: ['textEl']
51464             });
51465         }
51466         me.items.push(me.titleCmp);
51467
51468         // Add Tools
51469         me.items = me.items.concat(me.tools);
51470         this.callParent();
51471     },
51472
51473     initIconCmp: function() {
51474         this.iconCmp = Ext.create('Ext.Component', {
51475             focusable: false,
51476             renderTpl : [
51477                 '<img id="{id}-iconEl" alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'
51478             ],
51479             renderData: {
51480                 blank  : Ext.BLANK_IMAGE_URL,
51481                 cls    : this.baseCls,
51482                 iconCls: this.iconCls,
51483                 orientation: this.orientation
51484             },
51485             childEls: ['iconEl'],
51486             iconCls: this.iconCls
51487         });
51488     },
51489
51490     afterRender: function() {
51491         var me = this;
51492
51493         me.el.unselectable();
51494         if (me.indicateDrag) {
51495             me.el.addCls(me.indicateDragCls);
51496         }
51497         me.mon(me.el, {
51498             click: me.onClick,
51499             scope: me
51500         });
51501         me.callParent();
51502     },
51503
51504     afterLayout: function() {
51505         var me = this;
51506         me.callParent(arguments);
51507
51508         // IE7 needs a forced repaint to make the top framing div expand to full width
51509         if (Ext.isIE7) {
51510             me.el.repaint();
51511         }
51512     },
51513
51514     // inherit docs
51515     addUIClsToElement: function(cls, force) {
51516         var me = this,
51517             result = me.callParent(arguments),
51518             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51519             array, i;
51520
51521         if (!force && me.rendered) {
51522             if (me.bodyCls) {
51523                 me.body.addCls(me.bodyCls);
51524             } else {
51525                 me.body.addCls(classes);
51526             }
51527         } else {
51528             if (me.bodyCls) {
51529                 array = me.bodyCls.split(' ');
51530
51531                 for (i = 0; i < classes.length; i++) {
51532                     if (!Ext.Array.contains(array, classes[i])) {
51533                         array.push(classes[i]);
51534                     }
51535                 }
51536
51537                 me.bodyCls = array.join(' ');
51538             } else {
51539                 me.bodyCls = classes.join(' ');
51540             }
51541         }
51542
51543         return result;
51544     },
51545
51546     // inherit docs
51547     removeUIClsFromElement: function(cls, force) {
51548         var me = this,
51549             result = me.callParent(arguments),
51550             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
51551             array, i;
51552
51553         if (!force && me.rendered) {
51554             if (me.bodyCls) {
51555                 me.body.removeCls(me.bodyCls);
51556             } else {
51557                 me.body.removeCls(classes);
51558             }
51559         } else {
51560             if (me.bodyCls) {
51561                 array = me.bodyCls.split(' ');
51562
51563                 for (i = 0; i < classes.length; i++) {
51564                     Ext.Array.remove(array, classes[i]);
51565                 }
51566
51567                 me.bodyCls = array.join(' ');
51568             }
51569         }
51570
51571        return result;
51572     },
51573
51574     // inherit docs
51575     addUIToElement: function(force) {
51576         var me = this,
51577             array, cls;
51578
51579         me.callParent(arguments);
51580
51581         cls = me.baseCls + '-body-' + me.ui;
51582         if (!force && me.rendered) {
51583             if (me.bodyCls) {
51584                 me.body.addCls(me.bodyCls);
51585             } else {
51586                 me.body.addCls(cls);
51587             }
51588         } else {
51589             if (me.bodyCls) {
51590                 array = me.bodyCls.split(' ');
51591
51592                 if (!Ext.Array.contains(array, cls)) {
51593                     array.push(cls);
51594                 }
51595
51596                 me.bodyCls = array.join(' ');
51597             } else {
51598                 me.bodyCls = cls;
51599             }
51600         }
51601
51602         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
51603             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
51604         }
51605     },
51606
51607     // inherit docs
51608     removeUIFromElement: function() {
51609         var me = this,
51610             array, cls;
51611
51612         me.callParent(arguments);
51613
51614         cls = me.baseCls + '-body-' + me.ui;
51615         if (me.rendered) {
51616             if (me.bodyCls) {
51617                 me.body.removeCls(me.bodyCls);
51618             } else {
51619                 me.body.removeCls(cls);
51620             }
51621         } else {
51622             if (me.bodyCls) {
51623                 array = me.bodyCls.split(' ');
51624                 Ext.Array.remove(array, cls);
51625                 me.bodyCls = array.join(' ');
51626             } else {
51627                 me.bodyCls = cls;
51628             }
51629         }
51630
51631         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
51632             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
51633         }
51634     },
51635
51636     onClick: function(e) {
51637         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
51638             this.fireEvent('click', e);
51639         }
51640     },
51641
51642     getTargetEl: function() {
51643         return this.body || this.frameBody || this.el;
51644     },
51645
51646     /**
51647      * Sets the title of the header.
51648      * @param {String} title The title to be set
51649      */
51650     setTitle: function(title) {
51651         var me = this;
51652         if (me.rendered) {
51653             if (me.titleCmp.rendered) {
51654                 if (me.titleCmp.surface) {
51655                     me.title = title || '';
51656                     var sprite = me.titleCmp.surface.items.items[0],
51657                         surface = me.titleCmp.surface;
51658
51659                     surface.remove(sprite);
51660                     me.textConfig.type = 'text';
51661                     me.textConfig.text = title;
51662                     sprite = surface.add(me.textConfig);
51663                     sprite.setAttributes({
51664                         rotate: {
51665                             degrees: 90
51666                         }
51667                     }, true);
51668                     me.titleCmp.autoSizeSurface();
51669                 } else {
51670                     me.title = title || '&#160;';
51671                     me.titleCmp.textEl.update(me.title);
51672                 }
51673             } else {
51674                 me.titleCmp.on({
51675                     render: function() {
51676                         me.setTitle(title);
51677                     },
51678                     single: true
51679                 });
51680             }
51681         } else {
51682             me.on({
51683                 render: function() {
51684                     me.layout.layout();
51685                     me.setTitle(title);
51686                 },
51687                 single: true
51688             });
51689         }
51690     },
51691
51692     /**
51693      * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
51694      * icon class if one has already been set.
51695      * @param {String} cls The new CSS class name
51696      */
51697     setIconCls: function(cls) {
51698         var me = this,
51699             isEmpty = !cls || !cls.length,
51700             iconCmp = me.iconCmp,
51701             el;
51702         
51703         me.iconCls = cls;
51704         if (!me.iconCmp && !isEmpty) {
51705             me.initIconCmp();
51706             me.insert(0, me.iconCmp);
51707         } else if (iconCmp) {
51708             if (isEmpty) {
51709                 me.iconCmp.destroy();
51710             } else {
51711                 el = iconCmp.iconEl;
51712                 el.removeCls(iconCmp.iconCls);
51713                 el.addCls(cls);
51714                 iconCmp.iconCls = cls;
51715             }
51716         }
51717     },
51718
51719     /**
51720      * Add a tool to the header
51721      * @param {Object} tool
51722      */
51723     addTool: function(tool) {
51724         this.tools.push(this.add(tool));
51725     },
51726
51727     /**
51728      * @private
51729      * Set up the tools.&lt;tool type> link in the owning Panel.
51730      * Bind the tool to its owning Panel.
51731      * @param component
51732      * @param index
51733      */
51734     onAdd: function(component, index) {
51735         this.callParent([arguments]);
51736         if (component instanceof Ext.panel.Tool) {
51737             component.bindTo(this.ownerCt);
51738             this.tools[component.type] = component;
51739         }
51740     }
51741 });
51742
51743 /**
51744  * @class Ext.fx.target.Element
51745  * @extends Ext.fx.target.Target
51746  * 
51747  * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
51748  * created directly, the {@link Ext.Element} will be passed to the animation and
51749  * and the appropriate target will be created.
51750  */
51751 Ext.define('Ext.fx.target.Element', {
51752
51753     /* Begin Definitions */
51754     
51755     extend: 'Ext.fx.target.Target',
51756     
51757     /* End Definitions */
51758
51759     type: 'element',
51760
51761     getElVal: function(el, attr, val) {
51762         if (val == undefined) {
51763             if (attr === 'x') {
51764                 val = el.getX();
51765             }
51766             else if (attr === 'y') {
51767                 val = el.getY();
51768             }
51769             else if (attr === 'scrollTop') {
51770                 val = el.getScroll().top;
51771             }
51772             else if (attr === 'scrollLeft') {
51773                 val = el.getScroll().left;
51774             }
51775             else if (attr === 'height') {
51776                 val = el.getHeight();
51777             }
51778             else if (attr === 'width') {
51779                 val = el.getWidth();
51780             }
51781             else {
51782                 val = el.getStyle(attr);
51783             }
51784         }
51785         return val;
51786     },
51787
51788     getAttr: function(attr, val) {
51789         var el = this.target;
51790         return [[ el, this.getElVal(el, attr, val)]];
51791     },
51792
51793     setAttr: function(targetData) {
51794         var target = this.target,
51795             ln = targetData.length,
51796             attrs, attr, o, i, j, ln2, element, value;
51797         for (i = 0; i < ln; i++) {
51798             attrs = targetData[i].attrs;
51799             for (attr in attrs) {
51800                 if (attrs.hasOwnProperty(attr)) {
51801                     ln2 = attrs[attr].length;
51802                     for (j = 0; j < ln2; j++) {
51803                         o = attrs[attr][j];
51804                         element = o[0];
51805                         value = o[1];
51806                         if (attr === 'x') {
51807                             element.setX(value);
51808                         }
51809                         else if (attr === 'y') {
51810                             element.setY(value);
51811                         }
51812                         else if (attr === 'scrollTop') {
51813                             element.scrollTo('top', value);
51814                         }
51815                         else if (attr === 'scrollLeft') {
51816                             element.scrollTo('left',value);
51817                         }
51818                         else {
51819                             element.setStyle(attr, value);
51820                         }
51821                     }
51822                 }
51823             }
51824         }
51825     }
51826 });
51827
51828 /**
51829  * @class Ext.fx.target.CompositeElement
51830  * @extends Ext.fx.target.Element
51831  * 
51832  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
51833  * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
51834  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
51835  * and the appropriate target will be created.
51836  */
51837 Ext.define('Ext.fx.target.CompositeElement', {
51838
51839     /* Begin Definitions */
51840
51841     extend: 'Ext.fx.target.Element',
51842
51843     /* End Definitions */
51844
51845     isComposite: true,
51846     
51847     constructor: function(target) {
51848         target.id = target.id || Ext.id(null, 'ext-composite-');
51849         this.callParent([target]);
51850     },
51851
51852     getAttr: function(attr, val) {
51853         var out = [],
51854             target = this.target;
51855         target.each(function(el) {
51856             out.push([el, this.getElVal(el, attr, val)]);
51857         }, this);
51858         return out;
51859     }
51860 });
51861
51862 /**
51863  * @class Ext.fx.Manager
51864  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
51865  * @private
51866  * @singleton
51867  */
51868
51869 Ext.define('Ext.fx.Manager', {
51870
51871     /* Begin Definitions */
51872
51873     singleton: true,
51874
51875     requires: ['Ext.util.MixedCollection',
51876                'Ext.fx.target.Element',
51877                'Ext.fx.target.CompositeElement',
51878                'Ext.fx.target.Sprite',
51879                'Ext.fx.target.CompositeSprite',
51880                'Ext.fx.target.Component'],
51881
51882     mixins: {
51883         queue: 'Ext.fx.Queue'
51884     },
51885
51886     /* End Definitions */
51887
51888     constructor: function() {
51889         this.items = Ext.create('Ext.util.MixedCollection');
51890         this.mixins.queue.constructor.call(this);
51891
51892         // this.requestAnimFrame = (function() {
51893         //     var raf = window.requestAnimationFrame ||
51894         //               window.webkitRequestAnimationFrame ||
51895         //               window.mozRequestAnimationFrame ||
51896         //               window.oRequestAnimationFrame ||
51897         //               window.msRequestAnimationFrame;
51898         //     if (raf) {
51899         //         return function(callback, element) {
51900         //             raf(callback);
51901         //         };
51902         //     }
51903         //     else {
51904         //         return function(callback, element) {
51905         //             window.setTimeout(callback, Ext.fx.Manager.interval);
51906         //         };
51907         //     }
51908         // })();
51909     },
51910
51911     /**
51912      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
51913      */
51914     interval: 16,
51915
51916     /**
51917      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
51918      */
51919     forceJS: true,
51920
51921     // @private Target factory
51922     createTarget: function(target) {
51923         var me = this,
51924             useCSS3 = !me.forceJS && Ext.supports.Transitions,
51925             targetObj;
51926
51927         me.useCSS3 = useCSS3;
51928
51929         // dom id
51930         if (Ext.isString(target)) {
51931             target = Ext.get(target);
51932         }
51933         // dom element
51934         if (target && target.tagName) {
51935             target = Ext.get(target);
51936             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51937             me.targets.add(targetObj);
51938             return targetObj;
51939         }
51940         if (Ext.isObject(target)) {
51941             // Element
51942             if (target.dom) {
51943                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51944             }
51945             // Element Composite
51946             else if (target.isComposite) {
51947                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
51948             }
51949             // Draw Sprite
51950             else if (target.isSprite) {
51951                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
51952             }
51953             // Draw Sprite Composite
51954             else if (target.isCompositeSprite) {
51955                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
51956             }
51957             // Component
51958             else if (target.isComponent) {
51959                 targetObj = Ext.create('Ext.fx.target.Component', target);
51960             }
51961             else if (target.isAnimTarget) {
51962                 return target;
51963             }
51964             else {
51965                 return null;
51966             }
51967             me.targets.add(targetObj);
51968             return targetObj;
51969         }
51970         else {
51971             return null;
51972         }
51973     },
51974
51975     /**
51976      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
51977      * @param {Ext.fx.Anim} anim
51978      */
51979     addAnim: function(anim) {
51980         var items = this.items,
51981             task = this.task;
51982         // var me = this,
51983         //     items = me.items,
51984         //     cb = function() {
51985         //         if (items.length) {
51986         //             me.task = true;
51987         //             me.runner();
51988         //             me.requestAnimFrame(cb);
51989         //         }
51990         //         else {
51991         //             me.task = false;
51992         //         }
51993         //     };
51994
51995         items.add(anim);
51996
51997         // Start the timer if not already running
51998         if (!task && items.length) {
51999             task = this.task = {
52000                 run: this.runner,
52001                 interval: this.interval,
52002                 scope: this
52003             };
52004             Ext.TaskManager.start(task);
52005         }
52006
52007         // //Start the timer if not already running
52008         // if (!me.task && items.length) {
52009         //     me.requestAnimFrame(cb);
52010         // }
52011     },
52012
52013     /**
52014      * Remove an Anim from the manager. This is done automatically when an Anim ends.
52015      * @param {Ext.fx.Anim} anim
52016      */
52017     removeAnim: function(anim) {
52018         // this.items.remove(anim);
52019         var items = this.items,
52020             task = this.task;
52021         items.remove(anim);
52022         // Stop the timer if there are no more managed Anims
52023         if (task && !items.length) {
52024             Ext.TaskManager.stop(task);
52025             delete this.task;
52026         }
52027     },
52028
52029     /**
52030      * @private
52031      * Filter function to determine which animations need to be started
52032      */
52033     startingFilter: function(o) {
52034         return o.paused === false && o.running === false && o.iterations > 0;
52035     },
52036
52037     /**
52038      * @private
52039      * Filter function to determine which animations are still running
52040      */
52041     runningFilter: function(o) {
52042         return o.paused === false && o.running === true && o.isAnimator !== true;
52043     },
52044
52045     /**
52046      * @private
52047      * Runner function being called each frame
52048      */
52049     runner: function() {
52050         var me = this,
52051             items = me.items;
52052
52053         me.targetData = {};
52054         me.targetArr = {};
52055
52056         // Single timestamp for all animations this interval
52057         me.timestamp = new Date();
52058
52059         // Start any items not current running
52060         items.filterBy(me.startingFilter).each(me.startAnim, me);
52061
52062         // Build the new attributes to be applied for all targets in this frame
52063         items.filterBy(me.runningFilter).each(me.runAnim, me);
52064
52065         // Apply all the pending changes to their targets
52066         me.applyPendingAttrs();
52067     },
52068
52069     /**
52070      * @private
52071      * Start the individual animation (initialization)
52072      */
52073     startAnim: function(anim) {
52074         anim.start(this.timestamp);
52075     },
52076
52077     /**
52078      * @private
52079      * Run the individual animation for this frame
52080      */
52081     runAnim: function(anim) {
52082         if (!anim) {
52083             return;
52084         }
52085         var me = this,
52086             targetId = anim.target.getId(),
52087             useCSS3 = me.useCSS3 && anim.target.type == 'element',
52088             elapsedTime = me.timestamp - anim.startTime,
52089             target, o;
52090
52091         this.collectTargetData(anim, elapsedTime, useCSS3);
52092
52093         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
52094         // to get a good initial state, then add the transition properties and set the final attributes.
52095         if (useCSS3) {
52096             // Flush the collected attributes, without transition
52097             anim.target.setAttr(me.targetData[targetId], true);
52098
52099             // Add the end frame data
52100             me.targetData[targetId] = [];
52101             me.collectTargetData(anim, anim.duration, useCSS3);
52102
52103             // Pause the animation so runAnim doesn't keep getting called
52104             anim.paused = true;
52105
52106             target = anim.target.target;
52107             // We only want to attach an event on the last element in a composite
52108             if (anim.target.isComposite) {
52109                 target = anim.target.target.last();
52110             }
52111
52112             // Listen for the transitionend event
52113             o = {};
52114             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
52115             o.scope = anim;
52116             o.single = true;
52117             target.on(o);
52118         }
52119         // For JS animation, trigger the lastFrame handler if this is the final frame
52120         else if (elapsedTime >= anim.duration) {
52121             me.applyPendingAttrs(true);
52122             delete me.targetData[targetId];
52123             delete me.targetArr[targetId];
52124             anim.lastFrame();
52125         }
52126     },
52127
52128     /**
52129      * Collect target attributes for the given Anim object at the given timestamp
52130      * @param {Ext.fx.Anim} anim The Anim instance
52131      * @param {Number} timestamp Time after the anim's start time
52132      */
52133     collectTargetData: function(anim, elapsedTime, useCSS3) {
52134         var targetId = anim.target.getId(),
52135             targetData = this.targetData[targetId],
52136             data;
52137         
52138         if (!targetData) {
52139             targetData = this.targetData[targetId] = [];
52140             this.targetArr[targetId] = anim.target;
52141         }
52142
52143         data = {
52144             duration: anim.duration,
52145             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
52146             attrs: {}
52147         };
52148         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
52149         targetData.push(data);
52150     },
52151
52152     /**
52153      * @private
52154      * Apply all pending attribute changes to their targets
52155      */
52156     applyPendingAttrs: function(isLastFrame) {
52157         var targetData = this.targetData,
52158             targetArr = this.targetArr,
52159             targetId;
52160         for (targetId in targetData) {
52161             if (targetData.hasOwnProperty(targetId)) {
52162                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
52163             }
52164         }
52165     }
52166 });
52167
52168 /**
52169  * @class Ext.fx.Animator
52170  *
52171  * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
52172  * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
52173  * at various points throughout the animation.
52174  *
52175  * ## Using Keyframes
52176  *
52177  * The {@link #keyframes} option is the most important part of specifying an animation when using this
52178  * class. A key frame is a point in a particular animation. We represent this as a percentage of the
52179  * total animation duration. At each key frame, we can specify the target values at that time. Note that
52180  * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
52181  * event that fires after each key frame is reached.
52182  *
52183  * ## Example
52184  *
52185  * In the example below, we modify the values of the element at each fifth throughout the animation.
52186  *
52187  *     @example
52188  *     Ext.create('Ext.fx.Animator', {
52189  *         target: Ext.getBody().createChild({
52190  *             style: {
52191  *                 width: '100px',
52192  *                 height: '100px',
52193  *                 'background-color': 'red'
52194  *             }
52195  *         }),
52196  *         duration: 10000, // 10 seconds
52197  *         keyframes: {
52198  *             0: {
52199  *                 opacity: 1,
52200  *                 backgroundColor: 'FF0000'
52201  *             },
52202  *             20: {
52203  *                 x: 30,
52204  *                 opacity: 0.5
52205  *             },
52206  *             40: {
52207  *                 x: 130,
52208  *                 backgroundColor: '0000FF'
52209  *             },
52210  *             60: {
52211  *                 y: 80,
52212  *                 opacity: 0.3
52213  *             },
52214  *             80: {
52215  *                 width: 200,
52216  *                 y: 200
52217  *             },
52218  *             100: {
52219  *                 opacity: 1,
52220  *                 backgroundColor: '00FF00'
52221  *             }
52222  *         }
52223  *     });
52224  */
52225 Ext.define('Ext.fx.Animator', {
52226
52227     /* Begin Definitions */
52228
52229     mixins: {
52230         observable: 'Ext.util.Observable'
52231     },
52232
52233     requires: ['Ext.fx.Manager'],
52234
52235     /* End Definitions */
52236
52237     isAnimator: true,
52238
52239     /**
52240      * @cfg {Number} duration
52241      * Time in milliseconds for the animation to last. Defaults to 250.
52242      */
52243     duration: 250,
52244
52245     /**
52246      * @cfg {Number} delay
52247      * Time to delay before starting the animation. Defaults to 0.
52248      */
52249     delay: 0,
52250
52251     /* private used to track a delayed starting time */
52252     delayStart: 0,
52253
52254     /**
52255      * @cfg {Boolean} dynamic
52256      * 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.
52257      */
52258     dynamic: false,
52259
52260     /**
52261      * @cfg {String} easing
52262      *
52263      * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
52264      * speed over its duration.
52265      *
52266      *  - backIn
52267      *  - backOut
52268      *  - bounceIn
52269      *  - bounceOut
52270      *  - ease
52271      *  - easeIn
52272      *  - easeOut
52273      *  - easeInOut
52274      *  - elasticIn
52275      *  - elasticOut
52276      *  - cubic-bezier(x1, y1, x2, y2)
52277      *
52278      * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
52279      * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
52280      * be in the range [0, 1] or the definition is invalid.
52281      *
52282      * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
52283      */
52284     easing: 'ease',
52285
52286     /**
52287      * Flag to determine if the animation has started
52288      * @property running
52289      * @type Boolean
52290      */
52291     running: false,
52292
52293     /**
52294      * Flag to determine if the animation is paused. Only set this to true if you need to
52295      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
52296      * @property paused
52297      * @type Boolean
52298      */
52299     paused: false,
52300
52301     /**
52302      * @private
52303      */
52304     damper: 1,
52305
52306     /**
52307      * @cfg {Number} iterations
52308      * Number of times to execute the animation. Defaults to 1.
52309      */
52310     iterations: 1,
52311
52312     /**
52313      * Current iteration the animation is running.
52314      * @property currentIteration
52315      * @type Number
52316      */
52317     currentIteration: 0,
52318
52319     /**
52320      * Current keyframe step of the animation.
52321      * @property keyframeStep
52322      * @type Number
52323      */
52324     keyframeStep: 0,
52325
52326     /**
52327      * @private
52328      */
52329     animKeyFramesRE: /^(from|to|\d+%?)$/,
52330
52331     /**
52332      * @cfg {Ext.fx.target.Target} target
52333      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
52334      * method to apply the same animation to many targets.
52335      */
52336
52337      /**
52338       * @cfg {Object} keyframes
52339       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
52340       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
52341       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
52342       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
52343       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
52344  <pre><code>
52345 keyframes : {
52346     '0%': {
52347         left: 100
52348     },
52349     '40%': {
52350         left: 150
52351     },
52352     '60%': {
52353         left: 75
52354     },
52355     '100%': {
52356         left: 100
52357     }
52358 }
52359  </code></pre>
52360       */
52361     constructor: function(config) {
52362         var me = this;
52363         config = Ext.apply(me, config || {});
52364         me.config = config;
52365         me.id = Ext.id(null, 'ext-animator-');
52366         me.addEvents(
52367             /**
52368              * @event beforeanimate
52369              * Fires before the animation starts. A handler can return false to cancel the animation.
52370              * @param {Ext.fx.Animator} this
52371              */
52372             'beforeanimate',
52373             /**
52374               * @event keyframe
52375               * Fires at each keyframe.
52376               * @param {Ext.fx.Animator} this
52377               * @param {Number} keyframe step number
52378               */
52379             'keyframe',
52380             /**
52381              * @event afteranimate
52382              * Fires when the animation is complete.
52383              * @param {Ext.fx.Animator} this
52384              * @param {Date} startTime
52385              */
52386             'afteranimate'
52387         );
52388         me.mixins.observable.constructor.call(me, config);
52389         me.timeline = [];
52390         me.createTimeline(me.keyframes);
52391         if (me.target) {
52392             me.applyAnimator(me.target);
52393             Ext.fx.Manager.addAnim(me);
52394         }
52395     },
52396
52397     /**
52398      * @private
52399      */
52400     sorter: function (a, b) {
52401         return a.pct - b.pct;
52402     },
52403
52404     /**
52405      * @private
52406      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
52407      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
52408      */
52409     createTimeline: function(keyframes) {
52410         var me = this,
52411             attrs = [],
52412             to = me.to || {},
52413             duration = me.duration,
52414             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
52415
52416         for (pct in keyframes) {
52417             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
52418                 attr = {attrs: Ext.apply(keyframes[pct], to)};
52419                 // CSS3 spec allow for from/to to be specified.
52420                 if (pct == "from") {
52421                     pct = 0;
52422                 }
52423                 else if (pct == "to") {
52424                     pct = 100;
52425                 }
52426                 // convert % values into integers
52427                 attr.pct = parseInt(pct, 10);
52428                 attrs.push(attr);
52429             }
52430         }
52431         // Sort by pct property
52432         Ext.Array.sort(attrs, me.sorter);
52433         // Only an end
52434         //if (attrs[0].pct) {
52435         //    attrs.unshift({pct: 0, attrs: element.attrs});
52436         //}
52437
52438         ln = attrs.length;
52439         for (i = 0; i < ln; i++) {
52440             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
52441             ms = duration * (attrs[i].pct / 100);
52442             me.timeline.push({
52443                 duration: ms - prevMs,
52444                 attrs: attrs[i].attrs
52445             });
52446         }
52447     },
52448
52449     /**
52450      * Applies animation to the Ext.fx.target
52451      * @private
52452      * @param target
52453      * @type String/Object
52454      */
52455     applyAnimator: function(target) {
52456         var me = this,
52457             anims = [],
52458             timeline = me.timeline,
52459             reverse = me.reverse,
52460             ln = timeline.length,
52461             anim, easing, damper, initial, attrs, lastAttrs, i;
52462
52463         if (me.fireEvent('beforeanimate', me) !== false) {
52464             for (i = 0; i < ln; i++) {
52465                 anim = timeline[i];
52466                 attrs = anim.attrs;
52467                 easing = attrs.easing || me.easing;
52468                 damper = attrs.damper || me.damper;
52469                 delete attrs.easing;
52470                 delete attrs.damper;
52471                 anim = Ext.create('Ext.fx.Anim', {
52472                     target: target,
52473                     easing: easing,
52474                     damper: damper,
52475                     duration: anim.duration,
52476                     paused: true,
52477                     to: attrs
52478                 });
52479                 anims.push(anim);
52480             }
52481             me.animations = anims;
52482             me.target = anim.target;
52483             for (i = 0; i < ln - 1; i++) {
52484                 anim = anims[i];
52485                 anim.nextAnim = anims[i + 1];
52486                 anim.on('afteranimate', function() {
52487                     this.nextAnim.paused = false;
52488                 });
52489                 anim.on('afteranimate', function() {
52490                     this.fireEvent('keyframe', this, ++this.keyframeStep);
52491                 }, me);
52492             }
52493             anims[ln - 1].on('afteranimate', function() {
52494                 this.lastFrame();
52495             }, me);
52496         }
52497     },
52498
52499     /**
52500      * @private
52501      * Fires beforeanimate and sets the running flag.
52502      */
52503     start: function(startTime) {
52504         var me = this,
52505             delay = me.delay,
52506             delayStart = me.delayStart,
52507             delayDelta;
52508         if (delay) {
52509             if (!delayStart) {
52510                 me.delayStart = startTime;
52511                 return;
52512             }
52513             else {
52514                 delayDelta = startTime - delayStart;
52515                 if (delayDelta < delay) {
52516                     return;
52517                 }
52518                 else {
52519                     // Compensate for frame delay;
52520                     startTime = new Date(delayStart.getTime() + delay);
52521                 }
52522             }
52523         }
52524         if (me.fireEvent('beforeanimate', me) !== false) {
52525             me.startTime = startTime;
52526             me.running = true;
52527             me.animations[me.keyframeStep].paused = false;
52528         }
52529     },
52530
52531     /**
52532      * @private
52533      * Perform lastFrame cleanup and handle iterations
52534      * @returns a hash of the new attributes.
52535      */
52536     lastFrame: function() {
52537         var me = this,
52538             iter = me.iterations,
52539             iterCount = me.currentIteration;
52540
52541         iterCount++;
52542         if (iterCount < iter) {
52543             me.startTime = new Date();
52544             me.currentIteration = iterCount;
52545             me.keyframeStep = 0;
52546             me.applyAnimator(me.target);
52547             me.animations[me.keyframeStep].paused = false;
52548         }
52549         else {
52550             me.currentIteration = 0;
52551             me.end();
52552         }
52553     },
52554
52555     /**
52556      * Fire afteranimate event and end the animation. Usually called automatically when the
52557      * animation reaches its final frame, but can also be called manually to pre-emptively
52558      * stop and destroy the running animation.
52559      */
52560     end: function() {
52561         var me = this;
52562         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
52563     }
52564 });
52565 /**
52566  * @class Ext.fx.Easing
52567  *
52568  * This class contains a series of function definitions used to modify values during an animation.
52569  * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
52570  * speed over its duration. The following options are available: 
52571  *
52572  * - linear The default easing type
52573  * - backIn
52574  * - backOut
52575  * - bounceIn
52576  * - bounceOut
52577  * - ease
52578  * - easeIn
52579  * - easeOut
52580  * - easeInOut
52581  * - elasticIn
52582  * - elasticOut
52583  * - cubic-bezier(x1, y1, x2, y2)
52584  *
52585  * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
52586  * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
52587  * be in the range [0, 1] or the definition is invalid.
52588  *
52589  * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
52590  *
52591  * @singleton
52592  */
52593 Ext.ns('Ext.fx');
52594
52595 Ext.require('Ext.fx.CubicBezier', function() {
52596     var math = Math,
52597         pi = math.PI,
52598         pow = math.pow,
52599         sin = math.sin,
52600         sqrt = math.sqrt,
52601         abs = math.abs,
52602         backInSeed = 1.70158;
52603     Ext.fx.Easing = {
52604         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
52605         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
52606         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
52607         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
52608         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
52609         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
52610         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
52611         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
52612     };
52613
52614     Ext.apply(Ext.fx.Easing, {
52615         linear: function(n) {
52616             return n;
52617         },
52618         ease: function(n) {
52619             var q = 0.07813 - n / 2,
52620                 alpha = -0.25,
52621                 Q = sqrt(0.0066 + q * q),
52622                 x = Q - q,
52623                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52624                 y = -Q - q,
52625                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52626                 t = X + Y + 0.25;
52627             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
52628         },
52629         easeIn: function (n) {
52630             return pow(n, 1.7);
52631         },
52632         easeOut: function (n) {
52633             return pow(n, 0.48);
52634         },
52635         easeInOut: function(n) {
52636             var q = 0.48 - n / 1.04,
52637                 Q = sqrt(0.1734 + q * q),
52638                 x = Q - q,
52639                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52640                 y = -Q - q,
52641                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52642                 t = X + Y + 0.5;
52643             return (1 - t) * 3 * t * t + t * t * t;
52644         },
52645         backIn: function (n) {
52646             return n * n * ((backInSeed + 1) * n - backInSeed);
52647         },
52648         backOut: function (n) {
52649             n = n - 1;
52650             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
52651         },
52652         elasticIn: function (n) {
52653             if (n === 0 || n === 1) {
52654                 return n;
52655             }
52656             var p = 0.3,
52657                 s = p / 4;
52658             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
52659         },
52660         elasticOut: function (n) {
52661             return 1 - Ext.fx.Easing.elasticIn(1 - n);
52662         },
52663         bounceIn: function (n) {
52664             return 1 - Ext.fx.Easing.bounceOut(1 - n);
52665         },
52666         bounceOut: function (n) {
52667             var s = 7.5625,
52668                 p = 2.75,
52669                 l;
52670             if (n < (1 / p)) {
52671                 l = s * n * n;
52672             } else {
52673                 if (n < (2 / p)) {
52674                     n -= (1.5 / p);
52675                     l = s * n * n + 0.75;
52676                 } else {
52677                     if (n < (2.5 / p)) {
52678                         n -= (2.25 / p);
52679                         l = s * n * n + 0.9375;
52680                     } else {
52681                         n -= (2.625 / p);
52682                         l = s * n * n + 0.984375;
52683                     }
52684                 }
52685             }
52686             return l;
52687         }
52688     });
52689     Ext.apply(Ext.fx.Easing, {
52690         'back-in': Ext.fx.Easing.backIn,
52691         'back-out': Ext.fx.Easing.backOut,
52692         'ease-in': Ext.fx.Easing.easeIn,
52693         'ease-out': Ext.fx.Easing.easeOut,
52694         'elastic-in': Ext.fx.Easing.elasticIn,
52695         'elastic-out': Ext.fx.Easing.elasticIn,
52696         'bounce-in': Ext.fx.Easing.bounceIn,
52697         'bounce-out': Ext.fx.Easing.bounceOut,
52698         'ease-in-out': Ext.fx.Easing.easeInOut
52699     });
52700 });
52701 /**
52702  * @class Ext.draw.Draw
52703  * Base Drawing class.  Provides base drawing functions.
52704  * @private
52705  */
52706 Ext.define('Ext.draw.Draw', {
52707     /* Begin Definitions */
52708
52709     singleton: true,
52710
52711     requires: ['Ext.draw.Color'],
52712
52713     /* End Definitions */
52714
52715     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
52716     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
52717     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
52718     stopsRE: /^(\d+%?)$/,
52719     radian: Math.PI / 180,
52720
52721     availableAnimAttrs: {
52722         along: "along",
52723         blur: null,
52724         "clip-rect": "csv",
52725         cx: null,
52726         cy: null,
52727         fill: "color",
52728         "fill-opacity": null,
52729         "font-size": null,
52730         height: null,
52731         opacity: null,
52732         path: "path",
52733         r: null,
52734         rotation: "csv",
52735         rx: null,
52736         ry: null,
52737         scale: "csv",
52738         stroke: "color",
52739         "stroke-opacity": null,
52740         "stroke-width": null,
52741         translation: "csv",
52742         width: null,
52743         x: null,
52744         y: null
52745     },
52746
52747     is: function(o, type) {
52748         type = String(type).toLowerCase();
52749         return (type == "object" && o === Object(o)) ||
52750             (type == "undefined" && typeof o == type) ||
52751             (type == "null" && o === null) ||
52752             (type == "array" && Array.isArray && Array.isArray(o)) ||
52753             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
52754     },
52755
52756     ellipsePath: function(sprite) {
52757         var attr = sprite.attr;
52758         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);
52759     },
52760
52761     rectPath: function(sprite) {
52762         var attr = sprite.attr;
52763         if (attr.radius) {
52764             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);
52765         }
52766         else {
52767             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
52768         }
52769     },
52770
52771     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
52772     path2string: function () {
52773         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52774     },
52775
52776     // Convert the passed arrayPath to a proper SVG path string (d attribute)
52777     pathToString: function(arrayPath) {
52778         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52779     },
52780
52781     parsePathString: function (pathString) {
52782         if (!pathString) {
52783             return null;
52784         }
52785         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
52786             data = [],
52787             me = this;
52788         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
52789             data = me.pathClone(pathString);
52790         }
52791         if (!data.length) {
52792             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
52793                 var params = [],
52794                     name = b.toLowerCase();
52795                 c.replace(me.pathValuesRE, function (a, b) {
52796                     b && params.push(+b);
52797                 });
52798                 if (name == "m" && params.length > 2) {
52799                     data.push([b].concat(Ext.Array.splice(params, 0, 2)));
52800                     name = "l";
52801                     b = (b == "m") ? "l" : "L";
52802                 }
52803                 while (params.length >= paramCounts[name]) {
52804                     data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
52805                     if (!paramCounts[name]) {
52806                         break;
52807                     }
52808                 }
52809             });
52810         }
52811         data.toString = me.path2string;
52812         return data;
52813     },
52814
52815     mapPath: function (path, matrix) {
52816         if (!matrix) {
52817             return path;
52818         }
52819         var x, y, i, ii, j, jj, pathi;
52820         path = this.path2curve(path);
52821         for (i = 0, ii = path.length; i < ii; i++) {
52822             pathi = path[i];
52823             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
52824                 x = matrix.x(pathi[j], pathi[j + 1]);
52825                 y = matrix.y(pathi[j], pathi[j + 1]);
52826                 pathi[j] = x;
52827                 pathi[j + 1] = y;
52828             }
52829         }
52830         return path;
52831     },
52832
52833     pathClone: function(pathArray) {
52834         var res = [],
52835             j, jj, i, ii;
52836         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52837             pathArray = this.parsePathString(pathArray);
52838         }
52839         for (i = 0, ii = pathArray.length; i < ii; i++) {
52840             res[i] = [];
52841             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
52842                 res[i][j] = pathArray[i][j];
52843             }
52844         }
52845         res.toString = this.path2string;
52846         return res;
52847     },
52848
52849     pathToAbsolute: function (pathArray) {
52850         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52851             pathArray = this.parsePathString(pathArray);
52852         }
52853         var res = [],
52854             x = 0,
52855             y = 0,
52856             mx = 0,
52857             my = 0,
52858             i = 0,
52859             ln = pathArray.length,
52860             r, pathSegment, j, ln2;
52861         // MoveTo initial x/y position
52862         if (ln && pathArray[0][0] == "M") {
52863             x = +pathArray[0][1];
52864             y = +pathArray[0][2];
52865             mx = x;
52866             my = y;
52867             i++;
52868             res[0] = ["M", x, y];
52869         }
52870         for (; i < ln; i++) {
52871             r = res[i] = [];
52872             pathSegment = pathArray[i];
52873             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
52874                 r[0] = pathSegment[0].toUpperCase();
52875                 switch (r[0]) {
52876                     // Elliptical Arc
52877                     case "A":
52878                         r[1] = pathSegment[1];
52879                         r[2] = pathSegment[2];
52880                         r[3] = pathSegment[3];
52881                         r[4] = pathSegment[4];
52882                         r[5] = pathSegment[5];
52883                         r[6] = +(pathSegment[6] + x);
52884                         r[7] = +(pathSegment[7] + y);
52885                         break;
52886                     // Vertical LineTo
52887                     case "V":
52888                         r[1] = +pathSegment[1] + y;
52889                         break;
52890                     // Horizontal LineTo
52891                     case "H":
52892                         r[1] = +pathSegment[1] + x;
52893                         break;
52894                     case "M":
52895                     // MoveTo
52896                         mx = +pathSegment[1] + x;
52897                         my = +pathSegment[2] + y;
52898                     default:
52899                         j = 1;
52900                         ln2 = pathSegment.length;
52901                         for (; j < ln2; j++) {
52902                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
52903                         }
52904                 }
52905             }
52906             else {
52907                 j = 0;
52908                 ln2 = pathSegment.length;
52909                 for (; j < ln2; j++) {
52910                     res[i][j] = pathSegment[j];
52911                 }
52912             }
52913             switch (r[0]) {
52914                 // ClosePath
52915                 case "Z":
52916                     x = mx;
52917                     y = my;
52918                     break;
52919                 // Horizontal LineTo
52920                 case "H":
52921                     x = r[1];
52922                     break;
52923                 // Vertical LineTo
52924                 case "V":
52925                     y = r[1];
52926                     break;
52927                 // MoveTo
52928                 case "M":
52929                     pathSegment = res[i];
52930                     ln2 = pathSegment.length;
52931                     mx = pathSegment[ln2 - 2];
52932                     my = pathSegment[ln2 - 1];
52933                 default:
52934                     pathSegment = res[i];
52935                     ln2 = pathSegment.length;
52936                     x = pathSegment[ln2 - 2];
52937                     y = pathSegment[ln2 - 1];
52938             }
52939         }
52940         res.toString = this.path2string;
52941         return res;
52942     },
52943
52944     // TO BE DEPRECATED
52945     pathToRelative: function (pathArray) {
52946         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
52947             pathArray = this.parsePathString(pathArray);
52948         }
52949         var res = [],
52950             x = 0,
52951             y = 0,
52952             mx = 0,
52953             my = 0,
52954             start = 0;
52955         if (pathArray[0][0] == "M") {
52956             x = pathArray[0][1];
52957             y = pathArray[0][2];
52958             mx = x;
52959             my = y;
52960             start++;
52961             res.push(["M", x, y]);
52962         }
52963         for (var i = start, ii = pathArray.length; i < ii; i++) {
52964             var r = res[i] = [],
52965                 pa = pathArray[i];
52966             if (pa[0] != pa[0].toLowerCase()) {
52967                 r[0] = pa[0].toLowerCase();
52968                 switch (r[0]) {
52969                     case "a":
52970                         r[1] = pa[1];
52971                         r[2] = pa[2];
52972                         r[3] = pa[3];
52973                         r[4] = pa[4];
52974                         r[5] = pa[5];
52975                         r[6] = +(pa[6] - x).toFixed(3);
52976                         r[7] = +(pa[7] - y).toFixed(3);
52977                         break;
52978                     case "v":
52979                         r[1] = +(pa[1] - y).toFixed(3);
52980                         break;
52981                     case "m":
52982                         mx = pa[1];
52983                         my = pa[2];
52984                     default:
52985                         for (var j = 1, jj = pa.length; j < jj; j++) {
52986                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
52987                         }
52988                 }
52989             } else {
52990                 r = res[i] = [];
52991                 if (pa[0] == "m") {
52992                     mx = pa[1] + x;
52993                     my = pa[2] + y;
52994                 }
52995                 for (var k = 0, kk = pa.length; k < kk; k++) {
52996                     res[i][k] = pa[k];
52997                 }
52998             }
52999             var len = res[i].length;
53000             switch (res[i][0]) {
53001                 case "z":
53002                     x = mx;
53003                     y = my;
53004                     break;
53005                 case "h":
53006                     x += +res[i][len - 1];
53007                     break;
53008                 case "v":
53009                     y += +res[i][len - 1];
53010                     break;
53011                 default:
53012                     x += +res[i][len - 2];
53013                     y += +res[i][len - 1];
53014             }
53015         }
53016         res.toString = this.path2string;
53017         return res;
53018     },
53019
53020     // Returns a path converted to a set of curveto commands
53021     path2curve: function (path) {
53022         var me = this,
53023             points = me.pathToAbsolute(path),
53024             ln = points.length,
53025             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
53026             i, seg, segLn, point;
53027             
53028         for (i = 0; i < ln; i++) {
53029             points[i] = me.command2curve(points[i], attrs);
53030             if (points[i].length > 7) {
53031                     points[i].shift();
53032                     point = points[i];
53033                     while (point.length) {
53034                         Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
53035                     }
53036                     Ext.Array.erase(points, i, 1);
53037                     ln = points.length;
53038                 }
53039             seg = points[i];
53040             segLn = seg.length;
53041             attrs.x = seg[segLn - 2];
53042             attrs.y = seg[segLn - 1];
53043             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
53044             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
53045         }
53046         return points;
53047     },
53048     
53049     interpolatePaths: function (path, path2) {
53050         var me = this,
53051             p = me.pathToAbsolute(path),
53052             p2 = me.pathToAbsolute(path2),
53053             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
53054             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
53055             fixArc = function (pp, i) {
53056                 if (pp[i].length > 7) {
53057                     pp[i].shift();
53058                     var pi = pp[i];
53059                     while (pi.length) {
53060                         Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
53061                     }
53062                     Ext.Array.erase(pp, i, 1);
53063                     ii = Math.max(p.length, p2.length || 0);
53064                 }
53065             },
53066             fixM = function (path1, path2, a1, a2, i) {
53067                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
53068                     Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
53069                     a1.bx = 0;
53070                     a1.by = 0;
53071                     a1.x = path1[i][1];
53072                     a1.y = path1[i][2];
53073                     ii = Math.max(p.length, p2.length || 0);
53074                 }
53075             };
53076         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
53077             p[i] = me.command2curve(p[i], attrs);
53078             fixArc(p, i);
53079             (p2[i] = me.command2curve(p2[i], attrs2));
53080             fixArc(p2, i);
53081             fixM(p, p2, attrs, attrs2, i);
53082             fixM(p2, p, attrs2, attrs, i);
53083             var seg = p[i],
53084                 seg2 = p2[i],
53085                 seglen = seg.length,
53086                 seg2len = seg2.length;
53087             attrs.x = seg[seglen - 2];
53088             attrs.y = seg[seglen - 1];
53089             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
53090             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
53091             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
53092             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
53093             attrs2.x = seg2[seg2len - 2];
53094             attrs2.y = seg2[seg2len - 1];
53095         }
53096         return [p, p2];
53097     },
53098     
53099     //Returns any path command as a curveto command based on the attrs passed
53100     command2curve: function (pathCommand, d) {
53101         var me = this;
53102         if (!pathCommand) {
53103             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
53104         }
53105         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
53106             d.qx = d.qy = null;
53107         }
53108         switch (pathCommand[0]) {
53109             case "M":
53110                 d.X = pathCommand[1];
53111                 d.Y = pathCommand[2];
53112                 break;
53113             case "A":
53114                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
53115                 break;
53116             case "S":
53117                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
53118                 break;
53119             case "T":
53120                 d.qx = d.x + (d.x - (d.qx || d.x));
53121                 d.qy = d.y + (d.y - (d.qy || d.y));
53122                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
53123                 break;
53124             case "Q":
53125                 d.qx = pathCommand[1];
53126                 d.qy = pathCommand[2];
53127                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
53128                 break;
53129             case "L":
53130                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
53131                 break;
53132             case "H":
53133                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
53134                 break;
53135             case "V":
53136                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
53137                 break;
53138             case "Z":
53139                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
53140                 break;
53141         }
53142         return pathCommand;
53143     },
53144
53145     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
53146         var _13 = 1 / 3,
53147             _23 = 2 / 3;
53148         return [
53149                 _13 * x1 + _23 * ax,
53150                 _13 * y1 + _23 * ay,
53151                 _13 * x2 + _23 * ax,
53152                 _13 * y2 + _23 * ay,
53153                 x2,
53154                 y2
53155             ];
53156     },
53157     
53158     rotate: function (x, y, rad) {
53159         var cos = Math.cos(rad),
53160             sin = Math.sin(rad),
53161             X = x * cos - y * sin,
53162             Y = x * sin + y * cos;
53163         return {x: X, y: Y};
53164     },
53165
53166     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
53167         // for more information of where this Math came from visit:
53168         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
53169         var me = this,
53170             PI = Math.PI,
53171             radian = me.radian,
53172             _120 = PI * 120 / 180,
53173             rad = radian * (+angle || 0),
53174             res = [],
53175             math = Math,
53176             mcos = math.cos,
53177             msin = math.sin,
53178             msqrt = math.sqrt,
53179             mabs = math.abs,
53180             masin = math.asin,
53181             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
53182             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
53183         if (!recursive) {
53184             xy = me.rotate(x1, y1, -rad);
53185             x1 = xy.x;
53186             y1 = xy.y;
53187             xy = me.rotate(x2, y2, -rad);
53188             x2 = xy.x;
53189             y2 = xy.y;
53190             cos = mcos(radian * angle);
53191             sin = msin(radian * angle);
53192             x = (x1 - x2) / 2;
53193             y = (y1 - y2) / 2;
53194             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
53195             if (h > 1) {
53196                 h = msqrt(h);
53197                 rx = h * rx;
53198                 ry = h * ry;
53199             }
53200             rx2 = rx * rx;
53201             ry2 = ry * ry;
53202             k = (large_arc_flag == sweep_flag ? -1 : 1) *
53203                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
53204             cx = k * rx * y / ry + (x1 + x2) / 2;
53205             cy = k * -ry * x / rx + (y1 + y2) / 2;
53206             f1 = masin(((y1 - cy) / ry).toFixed(7));
53207             f2 = masin(((y2 - cy) / ry).toFixed(7));
53208
53209             f1 = x1 < cx ? PI - f1 : f1;
53210             f2 = x2 < cx ? PI - f2 : f2;
53211             if (f1 < 0) {
53212                 f1 = PI * 2 + f1;
53213             }
53214             if (f2 < 0) {
53215                 f2 = PI * 2 + f2;
53216             }
53217             if (sweep_flag && f1 > f2) {
53218                 f1 = f1 - PI * 2;
53219             }
53220             if (!sweep_flag && f2 > f1) {
53221                 f2 = f2 - PI * 2;
53222             }
53223         }
53224         else {
53225             f1 = recursive[0];
53226             f2 = recursive[1];
53227             cx = recursive[2];
53228             cy = recursive[3];
53229         }
53230         df = f2 - f1;
53231         if (mabs(df) > _120) {
53232             f2old = f2;
53233             x2old = x2;
53234             y2old = y2;
53235             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
53236             x2 = cx + rx * mcos(f2);
53237             y2 = cy + ry * msin(f2);
53238             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
53239         }
53240         df = f2 - f1;
53241         c1 = mcos(f1);
53242         s1 = msin(f1);
53243         c2 = mcos(f2);
53244         s2 = msin(f2);
53245         t = math.tan(df / 4);
53246         hx = 4 / 3 * rx * t;
53247         hy = 4 / 3 * ry * t;
53248         m1 = [x1, y1];
53249         m2 = [x1 + hx * s1, y1 - hy * c1];
53250         m3 = [x2 + hx * s2, y2 - hy * c2];
53251         m4 = [x2, y2];
53252         m2[0] = 2 * m1[0] - m2[0];
53253         m2[1] = 2 * m1[1] - m2[1];
53254         if (recursive) {
53255             return [m2, m3, m4].concat(res);
53256         }
53257         else {
53258             res = [m2, m3, m4].concat(res).join().split(",");
53259             newres = [];
53260             ln = res.length;
53261             for (i = 0;  i < ln; i++) {
53262                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
53263             }
53264             return newres;
53265         }
53266     },
53267
53268     // TO BE DEPRECATED
53269     rotateAndTranslatePath: function (sprite) {
53270         var alpha = sprite.rotation.degrees,
53271             cx = sprite.rotation.x,
53272             cy = sprite.rotation.y,
53273             dx = sprite.translation.x,
53274             dy = sprite.translation.y,
53275             path,
53276             i,
53277             p,
53278             xy,
53279             j,
53280             res = [];
53281         if (!alpha && !dx && !dy) {
53282             return this.pathToAbsolute(sprite.attr.path);
53283         }
53284         dx = dx || 0;
53285         dy = dy || 0;
53286         path = this.pathToAbsolute(sprite.attr.path);
53287         for (i = path.length; i--;) {
53288             p = res[i] = path[i].slice();
53289             if (p[0] == "A") {
53290                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
53291                 p[6] = xy.x + dx;
53292                 p[7] = xy.y + dy;
53293             } else {
53294                 j = 1;
53295                 while (p[j + 1] != null) {
53296                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
53297                     p[j] = xy.x + dx;
53298                     p[j + 1] = xy.y + dy;
53299                     j += 2;
53300                 }
53301             }
53302         }
53303         return res;
53304     },
53305
53306     // TO BE DEPRECATED
53307     rotatePoint: function (x, y, alpha, cx, cy) {
53308         if (!alpha) {
53309             return {
53310                 x: x,
53311                 y: y
53312             };
53313         }
53314         cx = cx || 0;
53315         cy = cy || 0;
53316         x = x - cx;
53317         y = y - cy;
53318         alpha = alpha * this.radian;
53319         var cos = Math.cos(alpha),
53320             sin = Math.sin(alpha);
53321         return {
53322             x: x * cos - y * sin + cx,
53323             y: x * sin + y * cos + cy
53324         };
53325     },
53326
53327     pathDimensions: function (path) {
53328         if (!path || !(path + "")) {
53329             return {x: 0, y: 0, width: 0, height: 0};
53330         }
53331         path = this.path2curve(path);
53332         var x = 0, 
53333             y = 0,
53334             X = [],
53335             Y = [],
53336             i = 0,
53337             ln = path.length,
53338             p, xmin, ymin, dim;
53339         for (; i < ln; i++) {
53340             p = path[i];
53341             if (p[0] == "M") {
53342                 x = p[1];
53343                 y = p[2];
53344                 X.push(x);
53345                 Y.push(y);
53346             }
53347             else {
53348                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
53349                 X = X.concat(dim.min.x, dim.max.x);
53350                 Y = Y.concat(dim.min.y, dim.max.y);
53351                 x = p[5];
53352                 y = p[6];
53353             }
53354         }
53355         xmin = Math.min.apply(0, X);
53356         ymin = Math.min.apply(0, Y);
53357         return {
53358             x: xmin,
53359             y: ymin,
53360             path: path,
53361             width: Math.max.apply(0, X) - xmin,
53362             height: Math.max.apply(0, Y) - ymin
53363         };
53364     },
53365
53366     intersectInside: function(path, cp1, cp2) {
53367         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
53368     },
53369
53370     intersectIntersection: function(s, e, cp1, cp2) {
53371         var p = [],
53372             dcx = cp1[0] - cp2[0],
53373             dcy = cp1[1] - cp2[1],
53374             dpx = s[0] - e[0],
53375             dpy = s[1] - e[1],
53376             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
53377             n2 = s[0] * e[1] - s[1] * e[0],
53378             n3 = 1 / (dcx * dpy - dcy * dpx);
53379
53380         p[0] = (n1 * dpx - n2 * dcx) * n3;
53381         p[1] = (n1 * dpy - n2 * dcy) * n3;
53382         return p;
53383     },
53384
53385     intersect: function(subjectPolygon, clipPolygon) {
53386         var me = this,
53387             i = 0,
53388             ln = clipPolygon.length,
53389             cp1 = clipPolygon[ln - 1],
53390             outputList = subjectPolygon,
53391             cp2, s, e, point, ln2, inputList, j;
53392         for (; i < ln; ++i) {
53393             cp2 = clipPolygon[i];
53394             inputList = outputList;
53395             outputList = [];
53396             s = inputList[inputList.length - 1];
53397             j = 0;
53398             ln2 = inputList.length;
53399             for (; j < ln2; j++) {
53400                 e = inputList[j];
53401                 if (me.intersectInside(e, cp1, cp2)) {
53402                     if (!me.intersectInside(s, cp1, cp2)) {
53403                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
53404                     }
53405                     outputList.push(e);
53406                 }
53407                 else if (me.intersectInside(s, cp1, cp2)) {
53408                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
53409                 }
53410                 s = e;
53411             }
53412             cp1 = cp2;
53413         }
53414         return outputList;
53415     },
53416
53417     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
53418         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
53419             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
53420             c = p1x - c1x,
53421             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
53422             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
53423             y = [p1y, p2y],
53424             x = [p1x, p2x],
53425             dot;
53426         if (Math.abs(t1) > 1e12) {
53427             t1 = 0.5;
53428         }
53429         if (Math.abs(t2) > 1e12) {
53430             t2 = 0.5;
53431         }
53432         if (t1 > 0 && t1 < 1) {
53433             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
53434             x.push(dot.x);
53435             y.push(dot.y);
53436         }
53437         if (t2 > 0 && t2 < 1) {
53438             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
53439             x.push(dot.x);
53440             y.push(dot.y);
53441         }
53442         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
53443         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
53444         c = p1y - c1y;
53445         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
53446         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
53447         if (Math.abs(t1) > 1e12) {
53448             t1 = 0.5;
53449         }
53450         if (Math.abs(t2) > 1e12) {
53451             t2 = 0.5;
53452         }
53453         if (t1 > 0 && t1 < 1) {
53454             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
53455             x.push(dot.x);
53456             y.push(dot.y);
53457         }
53458         if (t2 > 0 && t2 < 1) {
53459             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
53460             x.push(dot.x);
53461             y.push(dot.y);
53462         }
53463         return {
53464             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
53465             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
53466         };
53467     },
53468
53469     /**
53470      * @private
53471      *
53472      * Calculates bezier curve control anchor points for a particular point in a path, with a
53473      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
53474      * Note that this algorithm assumes that the line being smoothed is normalized going from left
53475      * to right; it makes special adjustments assuming this orientation.
53476      *
53477      * @param {Number} prevX X coordinate of the previous point in the path
53478      * @param {Number} prevY Y coordinate of the previous point in the path
53479      * @param {Number} curX X coordinate of the current point in the path
53480      * @param {Number} curY Y coordinate of the current point in the path
53481      * @param {Number} nextX X coordinate of the next point in the path
53482      * @param {Number} nextY Y coordinate of the next point in the path
53483      * @param {Number} value A value to control the smoothness of the curve; this is used to
53484      * divide the distance between points, so a value of 2 corresponds to
53485      * half the distance between points (a very smooth line) while higher values
53486      * result in less smooth curves. Defaults to 4.
53487      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
53488      * are the control point for the curve toward the previous path point, and
53489      * x2 and y2 are the control point for the curve toward the next path point.
53490      */
53491     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
53492         value = value || 4;
53493         var M = Math,
53494             PI = M.PI,
53495             halfPI = PI / 2,
53496             abs = M.abs,
53497             sin = M.sin,
53498             cos = M.cos,
53499             atan = M.atan,
53500             control1Length, control2Length, control1Angle, control2Angle,
53501             control1X, control1Y, control2X, control2Y, alpha;
53502
53503         // Find the length of each control anchor line, by dividing the horizontal distance
53504         // between points by the value parameter.
53505         control1Length = (curX - prevX) / value;
53506         control2Length = (nextX - curX) / value;
53507
53508         // Determine the angle of each control anchor line. If the middle point is a vertical
53509         // turnaround then we force it to a flat horizontal angle to prevent the curve from
53510         // dipping above or below the middle point. Otherwise we use an angle that points
53511         // toward the previous/next target point.
53512         if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
53513             control1Angle = control2Angle = halfPI;
53514         } else {
53515             control1Angle = atan((curX - prevX) / abs(curY - prevY));
53516             if (prevY < curY) {
53517                 control1Angle = PI - control1Angle;
53518             }
53519             control2Angle = atan((nextX - curX) / abs(curY - nextY));
53520             if (nextY < curY) {
53521                 control2Angle = PI - control2Angle;
53522             }
53523         }
53524
53525         // Adjust the calculated angles so they point away from each other on the same line
53526         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
53527         if (alpha > halfPI) {
53528             alpha -= PI;
53529         }
53530         control1Angle += alpha;
53531         control2Angle += alpha;
53532
53533         // Find the control anchor points from the angles and length
53534         control1X = curX - control1Length * sin(control1Angle);
53535         control1Y = curY + control1Length * cos(control1Angle);
53536         control2X = curX + control2Length * sin(control2Angle);
53537         control2Y = curY + control2Length * cos(control2Angle);
53538
53539         // One last adjustment, make sure that no control anchor point extends vertically past
53540         // its target prev/next point, as that results in curves dipping above or below and
53541         // bending back strangely. If we find this happening we keep the control angle but
53542         // reduce the length of the control line so it stays within bounds.
53543         if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
53544             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
53545             control1Y = prevY;
53546         }
53547         if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
53548             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
53549             control2Y = nextY;
53550         }
53551         
53552         return {
53553             x1: control1X,
53554             y1: control1Y,
53555             x2: control2X,
53556             y2: control2Y
53557         };
53558     },
53559
53560     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
53561      * Defaults to a value of 4.
53562      */
53563     smooth: function (originalPath, value) {
53564         var path = this.path2curve(originalPath),
53565             newp = [path[0]],
53566             x = path[0][1],
53567             y = path[0][2],
53568             j,
53569             points,
53570             i = 1,
53571             ii = path.length,
53572             beg = 1,
53573             mx = x,
53574             my = y,
53575             cx = 0,
53576             cy = 0;
53577         for (; i < ii; i++) {
53578             var pathi = path[i],
53579                 pathil = pathi.length,
53580                 pathim = path[i - 1],
53581                 pathiml = pathim.length,
53582                 pathip = path[i + 1],
53583                 pathipl = pathip && pathip.length;
53584             if (pathi[0] == "M") {
53585                 mx = pathi[1];
53586                 my = pathi[2];
53587                 j = i + 1;
53588                 while (path[j][0] != "C") {
53589                     j++;
53590                 }
53591                 cx = path[j][5];
53592                 cy = path[j][6];
53593                 newp.push(["M", mx, my]);
53594                 beg = newp.length;
53595                 x = mx;
53596                 y = my;
53597                 continue;
53598             }
53599             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
53600                 var begl = newp[beg].length;
53601                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
53602                 newp[beg][1] = points.x2;
53603                 newp[beg][2] = points.y2;
53604             }
53605             else if (!pathip || pathip[0] == "M") {
53606                 points = {
53607                     x1: pathi[pathil - 2],
53608                     y1: pathi[pathil - 1]
53609                 };
53610             } else {
53611                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
53612             }
53613             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
53614             x = points.x2;
53615             y = points.y2;
53616         }
53617         return newp;
53618     },
53619
53620     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
53621         var t1 = 1 - t;
53622         return {
53623             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
53624             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
53625         };
53626     },
53627
53628     /**
53629      * A utility method to deduce an appropriate tick configuration for the data set of given
53630      * feature.
53631      * 
53632      * @param {Number/Date} from The minimum value in the data
53633      * @param {Number/Date} to The maximum value in the data
53634      * @param {Number} stepsMax The maximum number of ticks
53635      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
53636      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
53637      * @return {Number} return.from The result start value, which may be lower than the original start value
53638      * @return {Number} return.to The result end value, which may be higher than the original end value
53639      * @return {Number} return.power The calculate power.
53640      * @return {Number} return.step The value size of each step
53641      * @return {Number} return.steps The number of steps.
53642      */
53643     snapEnds: function (from, to, stepsMax) {
53644         if (Ext.isDate(from)) {
53645             return this.snapEndsByDate(from, to, stepsMax);
53646         }
53647         var step = (to - from) / stepsMax,
53648             level = Math.floor(Math.log(step) / Math.LN10) + 1,
53649             m = Math.pow(10, level),
53650             cur,
53651             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
53652             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
53653             stepCount = 0,
53654             value,
53655             weight,
53656             i,
53657             topValue,
53658             topWeight = 1e9,
53659             ln = interval.length;
53660         cur = from = Math.floor(from / m) * m;
53661         for (i = 0; i < ln; i++) {
53662             value = interval[i][0];
53663             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
53664             if (weight < topWeight) {
53665                 topValue = value;
53666                 topWeight = weight;
53667             }
53668         }
53669         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
53670         while (cur < to) {
53671             cur += step;
53672             stepCount++;
53673         }
53674         to = +cur.toFixed(10);
53675         return {
53676             from: from,
53677             to: to,
53678             power: level,
53679             step: step,
53680             steps: stepCount
53681         };
53682     },
53683
53684     /**
53685      * A utility method to deduce an appropriate tick configuration for the data set of given
53686      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
53687      *
53688      * @param {Date} from The minimum value in the data
53689      * @param {Date} to The maximum value in the data
53690      * @param {Number} stepsMax The maximum number of ticks
53691      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53692      * and will not be adjusted
53693      * @return {Object} The calculated step and ends info; properties are:
53694      * @return {Date} return.from The result start value, which may be lower than the original start value
53695      * @return {Date} return.to The result end value, which may be higher than the original end value
53696      * @return {Number} return.step The value size of each step
53697      * @return {Number} return.steps The number of steps.
53698      * NOTE: the steps may not divide the from/to range perfectly evenly;
53699      * there may be a smaller distance between the last step and the end value than between prior
53700      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
53701      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
53702      * `from` to find the correct point for each tick.
53703      */
53704     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
53705         var selectedStep = false, scales = [
53706                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
53707                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
53708                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
53709                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
53710                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
53711                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
53712             ], j, yearDiff;
53713
53714         // Find the most desirable scale
53715         Ext.each(scales, function(scale, i) {
53716             for (j = 0; j < scale[1].length; j++) {
53717                 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
53718                     selectedStep = [scale[0], scale[1][j]];
53719                     return false;
53720                 }
53721             }
53722         });
53723         if (!selectedStep) {
53724             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
53725             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
53726         }
53727         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
53728     },
53729
53730
53731     /**
53732      * A utility method to deduce an appropriate tick configuration for the data set of given
53733      * feature and specific step size.
53734      * @param {Date} from The minimum value in the data
53735      * @param {Date} to The maximum value in the data
53736      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
53737      * The second one is the number of units for the step (1, 2, etc.).
53738      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53739      * and will not be adjusted
53740      * @return {Object} See the return value of {@link #snapEndsByDate}.
53741      */
53742     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
53743         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
53744                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
53745             steps = 0, testFrom, testTo;
53746         if (lockEnds) {
53747             testFrom = from;
53748         } else {
53749             switch (step[0]) {
53750                 case Ext.Date.MILLI:
53751                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53752                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
53753                     break;
53754                 case Ext.Date.SECOND:
53755                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53756                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
53757                     break;
53758                 case Ext.Date.MINUTE:
53759                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53760                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
53761                     break;
53762                 case Ext.Date.HOUR:
53763                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
53764                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
53765                     break;
53766                 case Ext.Date.DAY:
53767                     testFrom = new Date(fromStat[0], fromStat[1],
53768                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
53769                     break;
53770                 case Ext.Date.MONTH:
53771                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
53772                     break;
53773                 default: // Ext.Date.YEAR
53774                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
53775                     break;
53776             }
53777         }
53778
53779         testTo = testFrom;
53780         // TODO(zhangbei) : We can do it better somehow...
53781         while (testTo < to) {
53782             testTo = Ext.Date.add(testTo, step[0], step[1]);
53783             steps++;
53784         }
53785
53786         if (lockEnds) {
53787             testTo = to;
53788         }
53789         return {
53790             from : +testFrom,
53791             to : +testTo,
53792             step : (testTo - testFrom) / steps,
53793             steps : steps
53794         };
53795     },
53796
53797     sorter: function (a, b) {
53798         return a.offset - b.offset;
53799     },
53800
53801     rad: function(degrees) {
53802         return degrees % 360 * Math.PI / 180;
53803     },
53804
53805     degrees: function(radian) {
53806         return radian * 180 / Math.PI % 360;
53807     },
53808
53809     withinBox: function(x, y, bbox) {
53810         bbox = bbox || {};
53811         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
53812     },
53813
53814     parseGradient: function(gradient) {
53815         var me = this,
53816             type = gradient.type || 'linear',
53817             angle = gradient.angle || 0,
53818             radian = me.radian,
53819             stops = gradient.stops,
53820             stopsArr = [],
53821             stop,
53822             vector,
53823             max,
53824             stopObj;
53825
53826         if (type == 'linear') {
53827             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
53828             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
53829             vector[2] *= max;
53830             vector[3] *= max;
53831             if (vector[2] < 0) {
53832                 vector[0] = -vector[2];
53833                 vector[2] = 0;
53834             }
53835             if (vector[3] < 0) {
53836                 vector[1] = -vector[3];
53837                 vector[3] = 0;
53838             }
53839         }
53840
53841         for (stop in stops) {
53842             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
53843                 stopObj = {
53844                     offset: parseInt(stop, 10),
53845                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
53846                     opacity: stops[stop].opacity || 1
53847                 };
53848                 stopsArr.push(stopObj);
53849             }
53850         }
53851         // Sort by pct property
53852         Ext.Array.sort(stopsArr, me.sorter);
53853         if (type == 'linear') {
53854             return {
53855                 id: gradient.id,
53856                 type: type,
53857                 vector: vector,
53858                 stops: stopsArr
53859             };
53860         }
53861         else {
53862             return {
53863                 id: gradient.id,
53864                 type: type,
53865                 centerX: gradient.centerX,
53866                 centerY: gradient.centerY,
53867                 focalX: gradient.focalX,
53868                 focalY: gradient.focalY,
53869                 radius: gradient.radius,
53870                 vector: vector,
53871                 stops: stopsArr
53872             };
53873         }
53874     }
53875 });
53876
53877
53878 /**
53879  * @class Ext.fx.PropertyHandler
53880  * @ignore
53881  */
53882 Ext.define('Ext.fx.PropertyHandler', {
53883
53884     /* Begin Definitions */
53885
53886     requires: ['Ext.draw.Draw'],
53887
53888     statics: {
53889         defaultHandler: {
53890             pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
53891             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
53892             scrollRE: /^scroll/i,
53893
53894             computeDelta: function(from, end, damper, initial, attr) {
53895                 damper = (typeof damper == 'number') ? damper : 1;
53896                 var unitRE = this.unitRE,
53897                     match = unitRE.exec(from),
53898                     start, units;
53899                 if (match) {
53900                     from = match[1];
53901                     units = match[2];
53902                     if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
53903                         units = 'px';
53904                     }
53905                 }
53906                 from = +from || 0;
53907
53908                 match = unitRE.exec(end);
53909                 if (match) {
53910                     end = match[1];
53911                     units = match[2] || units;
53912                 }
53913                 end = +end || 0;
53914                 start = (initial != null) ? initial : from;
53915                 return {
53916                     from: from,
53917                     delta: (end - start) * damper,
53918                     units: units
53919                 };
53920             },
53921
53922             get: function(from, end, damper, initialFrom, attr) {
53923                 var ln = from.length,
53924                     out = [],
53925                     i, initial, res, j, len;
53926                 for (i = 0; i < ln; i++) {
53927                     if (initialFrom) {
53928                         initial = initialFrom[i][1].from;
53929                     }
53930                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
53931                         res = [];
53932                         j = 0;
53933                         len = from[i][1].length;
53934                         for (; j < len; j++) {
53935                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
53936                         }
53937                         out.push([from[i][0], res]);
53938                     }
53939                     else {
53940                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
53941                     }
53942                 }
53943                 return out;
53944             },
53945
53946             set: function(values, easing) {
53947                 var ln = values.length,
53948                     out = [],
53949                     i, val, res, len, j;
53950                 for (i = 0; i < ln; i++) {
53951                     val  = values[i][1];
53952                     if (Ext.isArray(val)) {
53953                         res = [];
53954                         j = 0;
53955                         len = val.length;
53956                         for (; j < len; j++) {
53957                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
53958                         }
53959                         out.push([values[i][0], res]);
53960                     } else {
53961                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
53962                     }
53963                 }
53964                 return out;
53965             }
53966         },
53967         color: {
53968             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
53969             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
53970             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
53971
53972             parseColor : function(color, damper) {
53973                 damper = (typeof damper == 'number') ? damper : 1;
53974                 var base,
53975                     out = false,
53976                     match;
53977
53978                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
53979                     base = (idx % 2 == 0) ? 16 : 10;
53980                     match = re.exec(color);
53981                     if (match && match.length == 4) {
53982                         if (idx == 2) {
53983                             match[1] += match[1];
53984                             match[2] += match[2];
53985                             match[3] += match[3];
53986                         }
53987                         out = {
53988                             red: parseInt(match[1], base),
53989                             green: parseInt(match[2], base),
53990                             blue: parseInt(match[3], base)
53991                         };
53992                         return false;
53993                     }
53994                 });
53995                 return out || color;
53996             },
53997
53998             computeDelta: function(from, end, damper, initial) {
53999                 from = this.parseColor(from);
54000                 end = this.parseColor(end, damper);
54001                 var start = initial ? initial : from,
54002                     tfrom = typeof start,
54003                     tend = typeof end;
54004                 //Extra check for when the color string is not recognized.
54005                 if (tfrom == 'string' ||  tfrom == 'undefined' 
54006                   || tend == 'string' || tend == 'undefined') {
54007                     return end || start;
54008                 }
54009                 return {
54010                     from:  from,
54011                     delta: {
54012                         red: Math.round((end.red - start.red) * damper),
54013                         green: Math.round((end.green - start.green) * damper),
54014                         blue: Math.round((end.blue - start.blue) * damper)
54015                     }
54016                 };
54017             },
54018
54019             get: function(start, end, damper, initialFrom) {
54020                 var ln = start.length,
54021                     out = [],
54022                     i, initial;
54023                 for (i = 0; i < ln; i++) {
54024                     if (initialFrom) {
54025                         initial = initialFrom[i][1].from;
54026                     }
54027                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
54028                 }
54029                 return out;
54030             },
54031
54032             set: function(values, easing) {
54033                 var ln = values.length,
54034                     out = [],
54035                     i, val, parsedString, from, delta;
54036                 for (i = 0; i < ln; i++) {
54037                     val = values[i][1];
54038                     if (val) {
54039                         from = val.from;
54040                         delta = val.delta;
54041                         //multiple checks to reformat the color if it can't recognized by computeDelta.
54042                         val = (typeof val == 'object' && 'red' in val)? 
54043                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
54044                         val = (typeof val == 'object' && val.length)? val[0] : val;
54045                         if (typeof val == 'undefined') {
54046                             return [];
54047                         }
54048                         parsedString = typeof val == 'string'? val :
54049                             'rgb(' + [
54050                                   (from.red + Math.round(delta.red * easing)) % 256,
54051                                   (from.green + Math.round(delta.green * easing)) % 256,
54052                                   (from.blue + Math.round(delta.blue * easing)) % 256
54053                               ].join(',') + ')';
54054                         out.push([
54055                             values[i][0],
54056                             parsedString
54057                         ]);
54058                     }
54059                 }
54060                 return out;
54061             }
54062         },
54063         object: {
54064             interpolate: function(prop, damper) {
54065                 damper = (typeof damper == 'number') ? damper : 1;
54066                 var out = {},
54067                     p;
54068                 for(p in prop) {
54069                     out[p] = parseInt(prop[p], 10) * damper;
54070                 }
54071                 return out;
54072             },
54073
54074             computeDelta: function(from, end, damper, initial) {
54075                 from = this.interpolate(from);
54076                 end = this.interpolate(end, damper);
54077                 var start = initial ? initial : from,
54078                     delta = {},
54079                     p;
54080
54081                 for(p in end) {
54082                     delta[p] = end[p] - start[p];
54083                 }
54084                 return {
54085                     from:  from,
54086                     delta: delta
54087                 };
54088             },
54089
54090             get: function(start, end, damper, initialFrom) {
54091                 var ln = start.length,
54092                     out = [],
54093                     i, initial;
54094                 for (i = 0; i < ln; i++) {
54095                     if (initialFrom) {
54096                         initial = initialFrom[i][1].from;
54097                     }
54098                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
54099                 }
54100                 return out;
54101             },
54102
54103             set: function(values, easing) {
54104                 var ln = values.length,
54105                     out = [],
54106                     outObject = {},
54107                     i, from, delta, val, p;
54108                 for (i = 0; i < ln; i++) {
54109                     val  = values[i][1];
54110                     from = val.from;
54111                     delta = val.delta;
54112                     for (p in from) {
54113                         outObject[p] = Math.round(from[p] + delta[p] * easing);
54114                     }
54115                     out.push([
54116                         values[i][0],
54117                         outObject
54118                     ]);
54119                 }
54120                 return out;
54121             }
54122         },
54123
54124         path: {
54125             computeDelta: function(from, end, damper, initial) {
54126                 damper = (typeof damper == 'number') ? damper : 1;
54127                 var start;
54128                 from = +from || 0;
54129                 end = +end || 0;
54130                 start = (initial != null) ? initial : from;
54131                 return {
54132                     from: from,
54133                     delta: (end - start) * damper
54134                 };
54135             },
54136
54137             forcePath: function(path) {
54138                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
54139                     path = Ext.draw.Draw.parsePathString(path);
54140                 }
54141                 return path;
54142             },
54143
54144             get: function(start, end, damper, initialFrom) {
54145                 var endPath = this.forcePath(end),
54146                     out = [],
54147                     startLn = start.length,
54148                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
54149                 for (i = 0; i < startLn; i++) {
54150                     startPath = this.forcePath(start[i][1]);
54151
54152                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
54153                     startPath = deltaPath[0];
54154                     endPath = deltaPath[1];
54155
54156                     startPathLn = startPath.length;
54157                     path = [];
54158                     for (j = 0; j < startPathLn; j++) {
54159                         deltaPath = [startPath[j][0]];
54160                         pointsLn = startPath[j].length;
54161                         for (k = 1; k < pointsLn; k++) {
54162                             initial = initialFrom && initialFrom[0][1][j][k].from;
54163                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
54164                         }
54165                         path.push(deltaPath);
54166                     }
54167                     out.push([start[i][0], path]);
54168                 }
54169                 return out;
54170             },
54171
54172             set: function(values, easing) {
54173                 var ln = values.length,
54174                     out = [],
54175                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
54176                 for (i = 0; i < ln; i++) {
54177                     deltaPath = values[i][1];
54178                     newPath = [];
54179                     deltaPathLn = deltaPath.length;
54180                     for (j = 0; j < deltaPathLn; j++) {
54181                         calcPath = [deltaPath[j][0]];
54182                         pointsLn = deltaPath[j].length;
54183                         for (k = 1; k < pointsLn; k++) {
54184                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
54185                         }
54186                         newPath.push(calcPath.join(','));
54187                     }
54188                     out.push([values[i][0], newPath.join(',')]);
54189                 }
54190                 return out;
54191             }
54192         }
54193         /* End Definitions */
54194     }
54195 }, function() {
54196     Ext.each([
54197         'outlineColor',
54198         'backgroundColor',
54199         'borderColor',
54200         'borderTopColor',
54201         'borderRightColor', 
54202         'borderBottomColor', 
54203         'borderLeftColor',
54204         'fill',
54205         'stroke'
54206     ], function(prop) {
54207         this[prop] = this.color;
54208     }, this);
54209 });
54210 /**
54211  * @class Ext.fx.Anim
54212  *
54213  * This class manages animation for a specific {@link #target}. The animation allows
54214  * animation of various properties on the target, such as size, position, color and others.
54215  *
54216  * ## Starting Conditions
54217  * The starting conditions for the animation are provided by the {@link #from} configuration.
54218  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
54219  * property is not defined, the starting value for that property will be read directly from the target.
54220  *
54221  * ## End Conditions
54222  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
54223  * the final values once the animations has finished. The values in the {@link #from} can mirror
54224  * those in the {@link #to} configuration to provide a starting point.
54225  *
54226  * ## Other Options
54227  *  - {@link #duration}: Specifies the time period of the animation.
54228  *  - {@link #easing}: Specifies the easing of the animation.
54229  *  - {@link #iterations}: Allows the animation to repeat a number of times.
54230  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
54231  *
54232  * ## Example Code
54233  *
54234  *     @example
54235  *     var myComponent = Ext.create('Ext.Component', {
54236  *         renderTo: document.body,
54237  *         width: 200,
54238  *         height: 200,
54239  *         style: 'border: 1px solid red;'
54240  *     });
54241  *
54242  *     Ext.create('Ext.fx.Anim', {
54243  *         target: myComponent,
54244  *         duration: 1000,
54245  *         from: {
54246  *             width: 400 //starting width 400
54247  *         },
54248  *         to: {
54249  *             width: 300, //end width 300
54250  *             height: 300 // end width 300
54251  *         }
54252  *     });
54253  */
54254 Ext.define('Ext.fx.Anim', {
54255
54256     /* Begin Definitions */
54257
54258     mixins: {
54259         observable: 'Ext.util.Observable'
54260     },
54261
54262     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
54263
54264     /* End Definitions */
54265
54266     isAnimation: true,
54267
54268     /**
54269      * @cfg {Function} callback
54270      * A function to be run after the animation has completed.
54271      */
54272
54273     /**
54274      * @cfg {Function} scope
54275      * The scope that the {@link #callback} function will be called with
54276      */
54277
54278     /**
54279      * @cfg {Number} duration
54280      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
54281      * specified, then each animate will take the same duration for each iteration.
54282      */
54283     duration: 250,
54284
54285     /**
54286      * @cfg {Number} delay
54287      * Time to delay before starting the animation. Defaults to 0.
54288      */
54289     delay: 0,
54290
54291     /* private used to track a delayed starting time */
54292     delayStart: 0,
54293
54294     /**
54295      * @cfg {Boolean} dynamic
54296      * 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.
54297      */
54298     dynamic: false,
54299
54300     /**
54301      * @cfg {String} easing
54302 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
54303 speed over its duration.
54304
54305          -backIn
54306          -backOut
54307          -bounceIn
54308          -bounceOut
54309          -ease
54310          -easeIn
54311          -easeOut
54312          -easeInOut
54313          -elasticIn
54314          -elasticOut
54315          -cubic-bezier(x1, y1, x2, y2)
54316
54317 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
54318 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
54319 be in the range [0, 1] or the definition is invalid.
54320
54321 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
54322
54323      * @markdown
54324      */
54325     easing: 'ease',
54326
54327      /**
54328       * @cfg {Object} keyframes
54329       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
54330       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
54331       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
54332       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
54333       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
54334  <pre><code>
54335 keyframes : {
54336     '0%': {
54337         left: 100
54338     },
54339     '40%': {
54340         left: 150
54341     },
54342     '60%': {
54343         left: 75
54344     },
54345     '100%': {
54346         left: 100
54347     }
54348 }
54349  </code></pre>
54350       */
54351
54352     /**
54353      * @private
54354      */
54355     damper: 1,
54356
54357     /**
54358      * @private
54359      */
54360     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
54361
54362     /**
54363      * Run the animation from the end to the beginning
54364      * Defaults to false.
54365      * @cfg {Boolean} reverse
54366      */
54367     reverse: false,
54368
54369     /**
54370      * Flag to determine if the animation has started
54371      * @property running
54372      * @type Boolean
54373      */
54374     running: false,
54375
54376     /**
54377      * Flag to determine if the animation is paused. Only set this to true if you need to
54378      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
54379      * @property paused
54380      * @type Boolean
54381      */
54382     paused: false,
54383
54384     /**
54385      * Number of times to execute the animation. Defaults to 1.
54386      * @cfg {Number} iterations
54387      */
54388     iterations: 1,
54389
54390     /**
54391      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
54392      * @cfg {Boolean} alternate
54393      * Defaults to false.
54394      */
54395     alternate: false,
54396
54397     /**
54398      * Current iteration the animation is running.
54399      * @property currentIteration
54400      * @type Number
54401      */
54402     currentIteration: 0,
54403
54404     /**
54405      * Starting time of the animation.
54406      * @property startTime
54407      * @type Date
54408      */
54409     startTime: 0,
54410
54411     /**
54412      * Contains a cache of the interpolators to be used.
54413      * @private
54414      * @property propHandlers
54415      * @type Object
54416      */
54417
54418     /**
54419      * @cfg {String/Object} target
54420      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
54421      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
54422      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
54423      * automatically.
54424      */
54425
54426     /**
54427      * @cfg {Object} from
54428      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
54429      * Ext.fx.target will be used. For example:
54430 <pre><code>
54431 from : {
54432     opacity: 0,       // Transparent
54433     color: '#ffffff', // White
54434     left: 0
54435 }
54436 </code></pre>
54437      */
54438
54439     /**
54440      * @cfg {Object} to
54441      * An object containing property/value pairs for the end of the animation. For example:
54442  <pre><code>
54443  to : {
54444      opacity: 1,       // Opaque
54445      color: '#00ff00', // Green
54446      left: 500
54447  }
54448  </code></pre>
54449      */
54450
54451     // @private
54452     constructor: function(config) {
54453         var me = this,
54454             curve;
54455             
54456         config = config || {};
54457         // If keyframes are passed, they really want an Animator instead.
54458         if (config.keyframes) {
54459             return Ext.create('Ext.fx.Animator', config);
54460         }
54461         config = Ext.apply(me, config);
54462         if (me.from === undefined) {
54463             me.from = {};
54464         }
54465         me.propHandlers = {};
54466         me.config = config;
54467         me.target = Ext.fx.Manager.createTarget(me.target);
54468         me.easingFn = Ext.fx.Easing[me.easing];
54469         me.target.dynamic = me.dynamic;
54470
54471         // If not a pre-defined curve, try a cubic-bezier
54472         if (!me.easingFn) {
54473             me.easingFn = String(me.easing).match(me.bezierRE);
54474             if (me.easingFn && me.easingFn.length == 5) {
54475                 curve = me.easingFn;
54476                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
54477             }
54478         }
54479         me.id = Ext.id(null, 'ext-anim-');
54480         Ext.fx.Manager.addAnim(me);
54481         me.addEvents(
54482             /**
54483              * @event beforeanimate
54484              * Fires before the animation starts. A handler can return false to cancel the animation.
54485              * @param {Ext.fx.Anim} this
54486              */
54487             'beforeanimate',
54488              /**
54489               * @event afteranimate
54490               * Fires when the animation is complete.
54491               * @param {Ext.fx.Anim} this
54492               * @param {Date} startTime
54493               */
54494             'afteranimate',
54495              /**
54496               * @event lastframe
54497               * Fires when the animation's last frame has been set.
54498               * @param {Ext.fx.Anim} this
54499               * @param {Date} startTime
54500               */
54501             'lastframe'
54502         );
54503         me.mixins.observable.constructor.call(me, config);
54504         if (config.callback) {
54505             me.on('afteranimate', config.callback, config.scope);
54506         }
54507         return me;
54508     },
54509
54510     /**
54511      * @private
54512      * Helper to the target
54513      */
54514     setAttr: function(attr, value) {
54515         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
54516     },
54517
54518     /**
54519      * @private
54520      * Set up the initial currentAttrs hash.
54521      */
54522     initAttrs: function() {
54523         var me = this,
54524             from = me.from,
54525             to = me.to,
54526             initialFrom = me.initialFrom || {},
54527             out = {},
54528             start, end, propHandler, attr;
54529
54530         for (attr in to) {
54531             if (to.hasOwnProperty(attr)) {
54532                 start = me.target.getAttr(attr, from[attr]);
54533                 end = to[attr];
54534                 // Use default (numeric) property handler
54535                 if (!Ext.fx.PropertyHandler[attr]) {
54536                     if (Ext.isObject(end)) {
54537                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
54538                     } else {
54539                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
54540                     }
54541                 }
54542                 // Use custom handler
54543                 else {
54544                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
54545                 }
54546                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
54547             }
54548         }
54549         me.currentAttrs = out;
54550     },
54551
54552     /**
54553      * @private
54554      * Fires beforeanimate and sets the running flag.
54555      */
54556     start: function(startTime) {
54557         var me = this,
54558             delay = me.delay,
54559             delayStart = me.delayStart,
54560             delayDelta;
54561         if (delay) {
54562             if (!delayStart) {
54563                 me.delayStart = startTime;
54564                 return;
54565             }
54566             else {
54567                 delayDelta = startTime - delayStart;
54568                 if (delayDelta < delay) {
54569                     return;
54570                 }
54571                 else {
54572                     // Compensate for frame delay;
54573                     startTime = new Date(delayStart.getTime() + delay);
54574                 }
54575             }
54576         }
54577         if (me.fireEvent('beforeanimate', me) !== false) {
54578             me.startTime = startTime;
54579             if (!me.paused && !me.currentAttrs) {
54580                 me.initAttrs();
54581             }
54582             me.running = true;
54583         }
54584     },
54585
54586     /**
54587      * @private
54588      * Calculate attribute value at the passed timestamp.
54589      * @returns a hash of the new attributes.
54590      */
54591     runAnim: function(elapsedTime) {
54592         var me = this,
54593             attrs = me.currentAttrs,
54594             duration = me.duration,
54595             easingFn = me.easingFn,
54596             propHandlers = me.propHandlers,
54597             ret = {},
54598             easing, values, attr, lastFrame;
54599
54600         if (elapsedTime >= duration) {
54601             elapsedTime = duration;
54602             lastFrame = true;
54603         }
54604         if (me.reverse) {
54605             elapsedTime = duration - elapsedTime;
54606         }
54607
54608         for (attr in attrs) {
54609             if (attrs.hasOwnProperty(attr)) {
54610                 values = attrs[attr];
54611                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
54612                 ret[attr] = propHandlers[attr].set(values, easing);
54613             }
54614         }
54615         return ret;
54616     },
54617
54618     /**
54619      * @private
54620      * Perform lastFrame cleanup and handle iterations
54621      * @returns a hash of the new attributes.
54622      */
54623     lastFrame: function() {
54624         var me = this,
54625             iter = me.iterations,
54626             iterCount = me.currentIteration;
54627
54628         iterCount++;
54629         if (iterCount < iter) {
54630             if (me.alternate) {
54631                 me.reverse = !me.reverse;
54632             }
54633             me.startTime = new Date();
54634             me.currentIteration = iterCount;
54635             // Turn off paused for CSS3 Transitions
54636             me.paused = false;
54637         }
54638         else {
54639             me.currentIteration = 0;
54640             me.end();
54641             me.fireEvent('lastframe', me, me.startTime);
54642         }
54643     },
54644
54645     /**
54646      * Fire afteranimate event and end the animation. Usually called automatically when the
54647      * animation reaches its final frame, but can also be called manually to pre-emptively
54648      * stop and destroy the running animation.
54649      */
54650     end: function() {
54651         var me = this;
54652         me.startTime = 0;
54653         me.paused = false;
54654         me.running = false;
54655         Ext.fx.Manager.removeAnim(me);
54656         me.fireEvent('afteranimate', me, me.startTime);
54657     }
54658 });
54659 // Set flag to indicate that Fx is available. Class might not be available immediately.
54660 Ext.enableFx = true;
54661
54662 /*
54663  * This is a derivative of the similarly named class in the YUI Library.
54664  * The original license:
54665  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
54666  * Code licensed under the BSD License:
54667  * http://developer.yahoo.net/yui/license.txt
54668  */
54669
54670
54671 /**
54672  * Defines the interface and base operation of items that that can be
54673  * dragged or can be drop targets.  It was designed to be extended, overriding
54674  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
54675  * Up to three html elements can be associated with a DragDrop instance:
54676  *
54677  * - linked element: the element that is passed into the constructor.
54678  *   This is the element which defines the boundaries for interaction with
54679  *   other DragDrop objects.
54680  *
54681  * - handle element(s): The drag operation only occurs if the element that
54682  *   was clicked matches a handle element.  By default this is the linked
54683  *   element, but there are times that you will want only a portion of the
54684  *   linked element to initiate the drag operation, and the setHandleElId()
54685  *   method provides a way to define this.
54686  *
54687  * - drag element: this represents the element that would be moved along
54688  *   with the cursor during a drag operation.  By default, this is the linked
54689  *   element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
54690  *   a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
54691  *
54692  * This class should not be instantiated until the onload event to ensure that
54693  * the associated elements are available.
54694  * The following would define a DragDrop obj that would interact with any
54695  * other DragDrop obj in the "group1" group:
54696  *
54697  *     dd = new Ext.dd.DragDrop("div1", "group1");
54698  *
54699  * Since none of the event handlers have been implemented, nothing would
54700  * actually happen if you were to run the code above.  Normally you would
54701  * override this class or one of the default implementations, but you can
54702  * also override the methods you want on an instance of the class...
54703  *
54704  *     dd.onDragDrop = function(e, id) {
54705  *         alert("dd was dropped on " + id);
54706  *     }
54707  *
54708  */
54709 Ext.define('Ext.dd.DragDrop', {
54710     requires: ['Ext.dd.DragDropManager'],
54711
54712     /**
54713      * Creates new DragDrop.
54714      * @param {String} id of the element that is linked to this instance
54715      * @param {String} sGroup the group of related DragDrop objects
54716      * @param {Object} config an object containing configurable attributes.
54717      * Valid properties for DragDrop:
54718      *
54719      * - padding
54720      * - isTarget
54721      * - maintainOffset
54722      * - primaryButtonOnly
54723      */
54724     constructor: function(id, sGroup, config) {
54725         if(id) {
54726             this.init(id, sGroup, config);
54727         }
54728     },
54729
54730     /**
54731      * Set to false to enable a DragDrop object to fire drag events while dragging
54732      * over its own Element. Defaults to true - DragDrop objects do not by default
54733      * fire drag events to themselves.
54734      * @property ignoreSelf
54735      * @type Boolean
54736      */
54737
54738     /**
54739      * The id of the element associated with this object.  This is what we
54740      * refer to as the "linked element" because the size and position of
54741      * this element is used to determine when the drag and drop objects have
54742      * interacted.
54743      * @property id
54744      * @type String
54745      */
54746     id: null,
54747
54748     /**
54749      * Configuration attributes passed into the constructor
54750      * @property config
54751      * @type Object
54752      */
54753     config: null,
54754
54755     /**
54756      * The id of the element that will be dragged.  By default this is same
54757      * as the linked element, but could be changed to another element. Ex:
54758      * Ext.dd.DDProxy
54759      * @property dragElId
54760      * @type String
54761      * @private
54762      */
54763     dragElId: null,
54764
54765     /**
54766      * The ID of the element that initiates the drag operation.  By default
54767      * this is the linked element, but could be changed to be a child of this
54768      * element.  This lets us do things like only starting the drag when the
54769      * header element within the linked html element is clicked.
54770      * @property handleElId
54771      * @type String
54772      * @private
54773      */
54774     handleElId: null,
54775
54776     /**
54777      * An object who's property names identify HTML tags to be considered invalid as drag handles.
54778      * A non-null property value identifies the tag as invalid. Defaults to the
54779      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
54780 {
54781     A: "A"
54782 }</code></pre>
54783      * @property invalidHandleTypes
54784      * @type Object
54785      */
54786     invalidHandleTypes: null,
54787
54788     /**
54789      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
54790      * A non-null property value identifies the ID as invalid. For example, to prevent
54791      * dragging from being initiated on element ID "foo", use:<pre><code>
54792 {
54793     foo: true
54794 }</code></pre>
54795      * @property invalidHandleIds
54796      * @type Object
54797      */
54798     invalidHandleIds: null,
54799
54800     /**
54801      * An Array of CSS class names for elements to be considered in valid as drag handles.
54802      * @property {String[]} invalidHandleClasses
54803      */
54804     invalidHandleClasses: null,
54805
54806     /**
54807      * The linked element's absolute X position at the time the drag was
54808      * started
54809      * @property startPageX
54810      * @type Number
54811      * @private
54812      */
54813     startPageX: 0,
54814
54815     /**
54816      * The linked element's absolute X position at the time the drag was
54817      * started
54818      * @property startPageY
54819      * @type Number
54820      * @private
54821      */
54822     startPageY: 0,
54823
54824     /**
54825      * The group defines a logical collection of DragDrop objects that are
54826      * related.  Instances only get events when interacting with other
54827      * DragDrop object in the same group.  This lets us define multiple
54828      * groups using a single DragDrop subclass if we want.
54829      * @property groups
54830      * @type Object An object in the format {'group1':true, 'group2':true}
54831      */
54832     groups: null,
54833
54834     /**
54835      * Individual drag/drop instances can be locked.  This will prevent
54836      * onmousedown start drag.
54837      * @property locked
54838      * @type Boolean
54839      * @private
54840      */
54841     locked: false,
54842
54843     /**
54844      * Locks this instance
54845      */
54846     lock: function() {
54847         this.locked = true;
54848     },
54849
54850     /**
54851      * When set to true, other DD objects in cooperating DDGroups do not receive
54852      * notification events when this DD object is dragged over them. Defaults to false.
54853      * @property moveOnly
54854      * @type Boolean
54855      */
54856     moveOnly: false,
54857
54858     /**
54859      * Unlocks this instace
54860      */
54861     unlock: function() {
54862         this.locked = false;
54863     },
54864
54865     /**
54866      * By default, all instances can be a drop target.  This can be disabled by
54867      * setting isTarget to false.
54868      * @property isTarget
54869      * @type Boolean
54870      */
54871     isTarget: true,
54872
54873     /**
54874      * The padding configured for this drag and drop object for calculating
54875      * the drop zone intersection with this object.
54876      * An array containing the 4 padding values: [top, right, bottom, left]
54877      * @property {Number[]} padding
54878      */
54879     padding: null,
54880
54881     /**
54882      * Cached reference to the linked element
54883      * @property _domRef
54884      * @private
54885      */
54886     _domRef: null,
54887
54888     /**
54889      * Internal typeof flag
54890      * @property __ygDragDrop
54891      * @private
54892      */
54893     __ygDragDrop: true,
54894
54895     /**
54896      * Set to true when horizontal contraints are applied
54897      * @property constrainX
54898      * @type Boolean
54899      * @private
54900      */
54901     constrainX: false,
54902
54903     /**
54904      * Set to true when vertical contraints are applied
54905      * @property constrainY
54906      * @type Boolean
54907      * @private
54908      */
54909     constrainY: false,
54910
54911     /**
54912      * The left constraint
54913      * @property minX
54914      * @type Number
54915      * @private
54916      */
54917     minX: 0,
54918
54919     /**
54920      * The right constraint
54921      * @property maxX
54922      * @type Number
54923      * @private
54924      */
54925     maxX: 0,
54926
54927     /**
54928      * The up constraint
54929      * @property minY
54930      * @type Number
54931      * @private
54932      */
54933     minY: 0,
54934
54935     /**
54936      * The down constraint
54937      * @property maxY
54938      * @type Number
54939      * @private
54940      */
54941     maxY: 0,
54942
54943     /**
54944      * Maintain offsets when we resetconstraints.  Set to true when you want
54945      * the position of the element relative to its parent to stay the same
54946      * when the page changes
54947      *
54948      * @property maintainOffset
54949      * @type Boolean
54950      */
54951     maintainOffset: false,
54952
54953     /**
54954      * Array of pixel locations the element will snap to if we specified a
54955      * horizontal graduation/interval.  This array is generated automatically
54956      * when you define a tick interval.
54957      * @property {Number[]} xTicks
54958      */
54959     xTicks: null,
54960
54961     /**
54962      * Array of pixel locations the element will snap to if we specified a
54963      * vertical graduation/interval.  This array is generated automatically
54964      * when you define a tick interval.
54965      * @property {Number[]} yTicks
54966      */
54967     yTicks: null,
54968
54969     /**
54970      * By default the drag and drop instance will only respond to the primary
54971      * button click (left button for a right-handed mouse).  Set to true to
54972      * allow drag and drop to start with any mouse click that is propogated
54973      * by the browser
54974      * @property primaryButtonOnly
54975      * @type Boolean
54976      */
54977     primaryButtonOnly: true,
54978
54979     /**
54980      * The available property is false until the linked dom element is accessible.
54981      * @property available
54982      * @type Boolean
54983      */
54984     available: false,
54985
54986     /**
54987      * By default, drags can only be initiated if the mousedown occurs in the
54988      * region the linked element is.  This is done in part to work around a
54989      * bug in some browsers that mis-report the mousedown if the previous
54990      * mouseup happened outside of the window.  This property is set to true
54991      * if outer handles are defined. Defaults to false.
54992      *
54993      * @property hasOuterHandles
54994      * @type Boolean
54995      */
54996     hasOuterHandles: false,
54997
54998     /**
54999      * Code that executes immediately before the startDrag event
55000      * @private
55001      */
55002     b4StartDrag: function(x, y) { },
55003
55004     /**
55005      * Abstract method called after a drag/drop object is clicked
55006      * and the drag or mousedown time thresholds have beeen met.
55007      * @param {Number} X click location
55008      * @param {Number} Y click location
55009      */
55010     startDrag: function(x, y) { /* override this */ },
55011
55012     /**
55013      * Code that executes immediately before the onDrag event
55014      * @private
55015      */
55016     b4Drag: function(e) { },
55017
55018     /**
55019      * Abstract method called during the onMouseMove event while dragging an
55020      * object.
55021      * @param {Event} e the mousemove event
55022      */
55023     onDrag: function(e) { /* override this */ },
55024
55025     /**
55026      * Abstract method called when this element fist begins hovering over
55027      * another DragDrop obj
55028      * @param {Event} e the mousemove event
55029      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55030      * id this is hovering over.  In INTERSECT mode, an array of one or more
55031      * dragdrop items being hovered over.
55032      */
55033     onDragEnter: function(e, id) { /* override this */ },
55034
55035     /**
55036      * Code that executes immediately before the onDragOver event
55037      * @private
55038      */
55039     b4DragOver: function(e) { },
55040
55041     /**
55042      * Abstract method called when this element is hovering over another
55043      * DragDrop obj
55044      * @param {Event} e the mousemove event
55045      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55046      * id this is hovering over.  In INTERSECT mode, an array of dd items
55047      * being hovered over.
55048      */
55049     onDragOver: function(e, id) { /* override this */ },
55050
55051     /**
55052      * Code that executes immediately before the onDragOut event
55053      * @private
55054      */
55055     b4DragOut: function(e) { },
55056
55057     /**
55058      * Abstract method called when we are no longer hovering over an element
55059      * @param {Event} e the mousemove event
55060      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55061      * id this was hovering over.  In INTERSECT mode, an array of dd items
55062      * that the mouse is no longer over.
55063      */
55064     onDragOut: function(e, id) { /* override this */ },
55065
55066     /**
55067      * Code that executes immediately before the onDragDrop event
55068      * @private
55069      */
55070     b4DragDrop: function(e) { },
55071
55072     /**
55073      * Abstract method called when this item is dropped on another DragDrop
55074      * obj
55075      * @param {Event} e the mouseup event
55076      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
55077      * id this was dropped on.  In INTERSECT mode, an array of dd items this
55078      * was dropped on.
55079      */
55080     onDragDrop: function(e, id) { /* override this */ },
55081
55082     /**
55083      * Abstract method called when this item is dropped on an area with no
55084      * drop target
55085      * @param {Event} e the mouseup event
55086      */
55087     onInvalidDrop: function(e) { /* override this */ },
55088
55089     /**
55090      * Code that executes immediately before the endDrag event
55091      * @private
55092      */
55093     b4EndDrag: function(e) { },
55094
55095     /**
55096      * Called when we are done dragging the object
55097      * @param {Event} e the mouseup event
55098      */
55099     endDrag: function(e) { /* override this */ },
55100
55101     /**
55102      * Code executed immediately before the onMouseDown event
55103      * @param {Event} e the mousedown event
55104      * @private
55105      */
55106     b4MouseDown: function(e) {  },
55107
55108     /**
55109      * Called when a drag/drop obj gets a mousedown
55110      * @param {Event} e the mousedown event
55111      */
55112     onMouseDown: function(e) { /* override this */ },
55113
55114     /**
55115      * Called when a drag/drop obj gets a mouseup
55116      * @param {Event} e the mouseup event
55117      */
55118     onMouseUp: function(e) { /* override this */ },
55119
55120     /**
55121      * Override the onAvailable method to do what is needed after the initial
55122      * position was determined.
55123      */
55124     onAvailable: function () {
55125     },
55126
55127     /**
55128      * @property {Object} defaultPadding
55129      * Provides default constraint padding to "constrainTo" elements.
55130      */
55131     defaultPadding: {
55132         left: 0,
55133         right: 0,
55134         top: 0,
55135         bottom: 0
55136     },
55137
55138     /**
55139      * Initializes the drag drop object's constraints to restrict movement to a certain element.
55140      *
55141      * Usage:
55142      *
55143      *     var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
55144      *                    { dragElId: "existingProxyDiv" });
55145      *     dd.startDrag = function(){
55146      *         this.constrainTo("parent-id");
55147      *     };
55148      *
55149      * Or you can initalize it using the {@link Ext.Element} object:
55150      *
55151      *     Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
55152      *         startDrag : function(){
55153      *             this.constrainTo("parent-id");
55154      *         }
55155      *     });
55156      *
55157      * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
55158      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
55159      * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
55160      * an object containing the sides to pad. For example: `{right:10, bottom:10}`
55161      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
55162      */
55163     constrainTo : function(constrainTo, pad, inContent){
55164         if(Ext.isNumber(pad)){
55165             pad = {left: pad, right:pad, top:pad, bottom:pad};
55166         }
55167         pad = pad || this.defaultPadding;
55168         var b = Ext.get(this.getEl()).getBox(),
55169             ce = Ext.get(constrainTo),
55170             s = ce.getScroll(),
55171             c,
55172             cd = ce.dom;
55173         if(cd == document.body){
55174             c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
55175         }else{
55176             var xy = ce.getXY();
55177             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
55178         }
55179
55180
55181         var topSpace = b.y - c.y,
55182             leftSpace = b.x - c.x;
55183
55184         this.resetConstraints();
55185         this.setXConstraint(leftSpace - (pad.left||0), // left
55186                 c.width - leftSpace - b.width - (pad.right||0), //right
55187                                 this.xTickSize
55188         );
55189         this.setYConstraint(topSpace - (pad.top||0), //top
55190                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
55191                                 this.yTickSize
55192         );
55193     },
55194
55195     /**
55196      * Returns a reference to the linked element
55197      * @return {HTMLElement} the html element
55198      */
55199     getEl: function() {
55200         if (!this._domRef) {
55201             this._domRef = Ext.getDom(this.id);
55202         }
55203
55204         return this._domRef;
55205     },
55206
55207     /**
55208      * Returns a reference to the actual element to drag.  By default this is
55209      * the same as the html element, but it can be assigned to another
55210      * element. An example of this can be found in Ext.dd.DDProxy
55211      * @return {HTMLElement} the html element
55212      */
55213     getDragEl: function() {
55214         return Ext.getDom(this.dragElId);
55215     },
55216
55217     /**
55218      * Sets up the DragDrop object.  Must be called in the constructor of any
55219      * Ext.dd.DragDrop subclass
55220      * @param {String} id the id of the linked element
55221      * @param {String} sGroup the group of related items
55222      * @param {Object} config configuration attributes
55223      */
55224     init: function(id, sGroup, config) {
55225         this.initTarget(id, sGroup, config);
55226         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
55227         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
55228     },
55229
55230     /**
55231      * Initializes Targeting functionality only... the object does not
55232      * get a mousedown handler.
55233      * @param {String} id the id of the linked element
55234      * @param {String} sGroup the group of related items
55235      * @param {Object} config configuration attributes
55236      */
55237     initTarget: function(id, sGroup, config) {
55238         // configuration attributes
55239         this.config = config || {};
55240
55241         // create a local reference to the drag and drop manager
55242         this.DDMInstance = Ext.dd.DragDropManager;
55243         // initialize the groups array
55244         this.groups = {};
55245
55246         // assume that we have an element reference instead of an id if the
55247         // parameter is not a string
55248         if (typeof id !== "string") {
55249             id = Ext.id(id);
55250         }
55251
55252         // set the id
55253         this.id = id;
55254
55255         // add to an interaction group
55256         this.addToGroup((sGroup) ? sGroup : "default");
55257
55258         // We don't want to register this as the handle with the manager
55259         // so we just set the id rather than calling the setter.
55260         this.handleElId = id;
55261
55262         // the linked element is the element that gets dragged by default
55263         this.setDragElId(id);
55264
55265         // by default, clicked anchors will not start drag operations.
55266         this.invalidHandleTypes = { A: "A" };
55267         this.invalidHandleIds = {};
55268         this.invalidHandleClasses = [];
55269
55270         this.applyConfig();
55271
55272         this.handleOnAvailable();
55273     },
55274
55275     /**
55276      * Applies the configuration parameters that were passed into the constructor.
55277      * This is supposed to happen at each level through the inheritance chain.  So
55278      * a DDProxy implentation will execute apply config on DDProxy, DD, and
55279      * DragDrop in order to get all of the parameters that are available in
55280      * each object.
55281      */
55282     applyConfig: function() {
55283
55284         // configurable properties:
55285         //    padding, isTarget, maintainOffset, primaryButtonOnly
55286         this.padding           = this.config.padding || [0, 0, 0, 0];
55287         this.isTarget          = (this.config.isTarget !== false);
55288         this.maintainOffset    = (this.config.maintainOffset);
55289         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
55290
55291     },
55292
55293     /**
55294      * Executed when the linked element is available
55295      * @private
55296      */
55297     handleOnAvailable: function() {
55298         this.available = true;
55299         this.resetConstraints();
55300         this.onAvailable();
55301     },
55302
55303     /**
55304      * Configures the padding for the target zone in px.  Effectively expands
55305      * (or reduces) the virtual object size for targeting calculations.
55306      * Supports css-style shorthand; if only one parameter is passed, all sides
55307      * will have that padding, and if only two are passed, the top and bottom
55308      * will have the first param, the left and right the second.
55309      * @param {Number} iTop    Top pad
55310      * @param {Number} iRight  Right pad
55311      * @param {Number} iBot    Bot pad
55312      * @param {Number} iLeft   Left pad
55313      */
55314     setPadding: function(iTop, iRight, iBot, iLeft) {
55315         // this.padding = [iLeft, iRight, iTop, iBot];
55316         if (!iRight && 0 !== iRight) {
55317             this.padding = [iTop, iTop, iTop, iTop];
55318         } else if (!iBot && 0 !== iBot) {
55319             this.padding = [iTop, iRight, iTop, iRight];
55320         } else {
55321             this.padding = [iTop, iRight, iBot, iLeft];
55322         }
55323     },
55324
55325     /**
55326      * Stores the initial placement of the linked element.
55327      * @param {Number} diffX   the X offset, default 0
55328      * @param {Number} diffY   the Y offset, default 0
55329      */
55330     setInitPosition: function(diffX, diffY) {
55331         var el = this.getEl();
55332
55333         if (!this.DDMInstance.verifyEl(el)) {
55334             return;
55335         }
55336
55337         var dx = diffX || 0;
55338         var dy = diffY || 0;
55339
55340         var p = Ext.Element.getXY( el );
55341
55342         this.initPageX = p[0] - dx;
55343         this.initPageY = p[1] - dy;
55344
55345         this.lastPageX = p[0];
55346         this.lastPageY = p[1];
55347
55348         this.setStartPosition(p);
55349     },
55350
55351     /**
55352      * Sets the start position of the element.  This is set when the obj
55353      * is initialized, the reset when a drag is started.
55354      * @param pos current position (from previous lookup)
55355      * @private
55356      */
55357     setStartPosition: function(pos) {
55358         var p = pos || Ext.Element.getXY( this.getEl() );
55359         this.deltaSetXY = null;
55360
55361         this.startPageX = p[0];
55362         this.startPageY = p[1];
55363     },
55364
55365     /**
55366      * Adds this instance to a group of related drag/drop objects.  All
55367      * instances belong to at least one group, and can belong to as many
55368      * groups as needed.
55369      * @param {String} sGroup the name of the group
55370      */
55371     addToGroup: function(sGroup) {
55372         this.groups[sGroup] = true;
55373         this.DDMInstance.regDragDrop(this, sGroup);
55374     },
55375
55376     /**
55377      * Removes this instance from the supplied interaction group
55378      * @param {String} sGroup  The group to drop
55379      */
55380     removeFromGroup: function(sGroup) {
55381         if (this.groups[sGroup]) {
55382             delete this.groups[sGroup];
55383         }
55384
55385         this.DDMInstance.removeDDFromGroup(this, sGroup);
55386     },
55387
55388     /**
55389      * Allows you to specify that an element other than the linked element
55390      * will be moved with the cursor during a drag
55391      * @param {String} id the id of the element that will be used to initiate the drag
55392      */
55393     setDragElId: function(id) {
55394         this.dragElId = id;
55395     },
55396
55397     /**
55398      * Allows you to specify a child of the linked element that should be
55399      * used to initiate the drag operation.  An example of this would be if
55400      * you have a content div with text and links.  Clicking anywhere in the
55401      * content area would normally start the drag operation.  Use this method
55402      * to specify that an element inside of the content div is the element
55403      * that starts the drag operation.
55404      * @param {String} id the id of the element that will be used to
55405      * initiate the drag.
55406      */
55407     setHandleElId: function(id) {
55408         if (typeof id !== "string") {
55409             id = Ext.id(id);
55410         }
55411         this.handleElId = id;
55412         this.DDMInstance.regHandle(this.id, id);
55413     },
55414
55415     /**
55416      * Allows you to set an element outside of the linked element as a drag
55417      * handle
55418      * @param {String} id the id of the element that will be used to initiate the drag
55419      */
55420     setOuterHandleElId: function(id) {
55421         if (typeof id !== "string") {
55422             id = Ext.id(id);
55423         }
55424         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
55425         this.setHandleElId(id);
55426
55427         this.hasOuterHandles = true;
55428     },
55429
55430     /**
55431      * Removes all drag and drop hooks for this element
55432      */
55433     unreg: function() {
55434         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
55435         this._domRef = null;
55436         this.DDMInstance._remove(this);
55437     },
55438
55439     destroy : function(){
55440         this.unreg();
55441     },
55442
55443     /**
55444      * Returns true if this instance is locked, or the drag drop mgr is locked
55445      * (meaning that all drag/drop is disabled on the page.)
55446      * @return {Boolean} true if this obj or all drag/drop is locked, else
55447      * false
55448      */
55449     isLocked: function() {
55450         return (this.DDMInstance.isLocked() || this.locked);
55451     },
55452
55453     /**
55454      * Called when this object is clicked
55455      * @param {Event} e
55456      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
55457      * @private
55458      */
55459     handleMouseDown: function(e, oDD){
55460         if (this.primaryButtonOnly && e.button != 0) {
55461             return;
55462         }
55463
55464         if (this.isLocked()) {
55465             return;
55466         }
55467
55468         this.DDMInstance.refreshCache(this.groups);
55469
55470         var pt = e.getPoint();
55471         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
55472         } else {
55473             if (this.clickValidator(e)) {
55474                 // set the initial element position
55475                 this.setStartPosition();
55476                 this.b4MouseDown(e);
55477                 this.onMouseDown(e);
55478
55479                 this.DDMInstance.handleMouseDown(e, this);
55480
55481                 this.DDMInstance.stopEvent(e);
55482             } else {
55483
55484
55485             }
55486         }
55487     },
55488
55489     clickValidator: function(e) {
55490         var target = e.getTarget();
55491         return ( this.isValidHandleChild(target) &&
55492                     (this.id == this.handleElId ||
55493                         this.DDMInstance.handleWasClicked(target, this.id)) );
55494     },
55495
55496     /**
55497      * Allows you to specify a tag name that should not start a drag operation
55498      * when clicked.  This is designed to facilitate embedding links within a
55499      * drag handle that do something other than start the drag.
55500      * @method addInvalidHandleType
55501      * @param {String} tagName the type of element to exclude
55502      */
55503     addInvalidHandleType: function(tagName) {
55504         var type = tagName.toUpperCase();
55505         this.invalidHandleTypes[type] = type;
55506     },
55507
55508     /**
55509      * Lets you to specify an element id for a child of a drag handle
55510      * that should not initiate a drag
55511      * @method addInvalidHandleId
55512      * @param {String} id the element id of the element you wish to ignore
55513      */
55514     addInvalidHandleId: function(id) {
55515         if (typeof id !== "string") {
55516             id = Ext.id(id);
55517         }
55518         this.invalidHandleIds[id] = id;
55519     },
55520
55521     /**
55522      * Lets you specify a css class of elements that will not initiate a drag
55523      * @param {String} cssClass the class of the elements you wish to ignore
55524      */
55525     addInvalidHandleClass: function(cssClass) {
55526         this.invalidHandleClasses.push(cssClass);
55527     },
55528
55529     /**
55530      * Unsets an excluded tag name set by addInvalidHandleType
55531      * @param {String} tagName the type of element to unexclude
55532      */
55533     removeInvalidHandleType: function(tagName) {
55534         var type = tagName.toUpperCase();
55535         // this.invalidHandleTypes[type] = null;
55536         delete this.invalidHandleTypes[type];
55537     },
55538
55539     /**
55540      * Unsets an invalid handle id
55541      * @param {String} id the id of the element to re-enable
55542      */
55543     removeInvalidHandleId: function(id) {
55544         if (typeof id !== "string") {
55545             id = Ext.id(id);
55546         }
55547         delete this.invalidHandleIds[id];
55548     },
55549
55550     /**
55551      * Unsets an invalid css class
55552      * @param {String} cssClass the class of the element(s) you wish to
55553      * re-enable
55554      */
55555     removeInvalidHandleClass: function(cssClass) {
55556         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
55557             if (this.invalidHandleClasses[i] == cssClass) {
55558                 delete this.invalidHandleClasses[i];
55559             }
55560         }
55561     },
55562
55563     /**
55564      * Checks the tag exclusion list to see if this click should be ignored
55565      * @param {HTMLElement} node the HTMLElement to evaluate
55566      * @return {Boolean} true if this is a valid tag type, false if not
55567      */
55568     isValidHandleChild: function(node) {
55569
55570         var valid = true;
55571         // var n = (node.nodeName == "#text") ? node.parentNode : node;
55572         var nodeName;
55573         try {
55574             nodeName = node.nodeName.toUpperCase();
55575         } catch(e) {
55576             nodeName = node.nodeName;
55577         }
55578         valid = valid && !this.invalidHandleTypes[nodeName];
55579         valid = valid && !this.invalidHandleIds[node.id];
55580
55581         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
55582             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
55583         }
55584
55585
55586         return valid;
55587
55588     },
55589
55590     /**
55591      * Creates the array of horizontal tick marks if an interval was specified
55592      * in setXConstraint().
55593      * @private
55594      */
55595     setXTicks: function(iStartX, iTickSize) {
55596         this.xTicks = [];
55597         this.xTickSize = iTickSize;
55598
55599         var tickMap = {};
55600
55601         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
55602             if (!tickMap[i]) {
55603                 this.xTicks[this.xTicks.length] = i;
55604                 tickMap[i] = true;
55605             }
55606         }
55607
55608         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
55609             if (!tickMap[i]) {
55610                 this.xTicks[this.xTicks.length] = i;
55611                 tickMap[i] = true;
55612             }
55613         }
55614
55615         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
55616     },
55617
55618     /**
55619      * Creates the array of vertical tick marks if an interval was specified in
55620      * setYConstraint().
55621      * @private
55622      */
55623     setYTicks: function(iStartY, iTickSize) {
55624         this.yTicks = [];
55625         this.yTickSize = iTickSize;
55626
55627         var tickMap = {};
55628
55629         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
55630             if (!tickMap[i]) {
55631                 this.yTicks[this.yTicks.length] = i;
55632                 tickMap[i] = true;
55633             }
55634         }
55635
55636         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
55637             if (!tickMap[i]) {
55638                 this.yTicks[this.yTicks.length] = i;
55639                 tickMap[i] = true;
55640             }
55641         }
55642
55643         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
55644     },
55645
55646     /**
55647      * By default, the element can be dragged any place on the screen.  Use
55648      * this method to limit the horizontal travel of the element.  Pass in
55649      * 0,0 for the parameters if you want to lock the drag to the y axis.
55650      * @param {Number} iLeft the number of pixels the element can move to the left
55651      * @param {Number} iRight the number of pixels the element can move to the
55652      * right
55653      * @param {Number} iTickSize (optional) parameter for specifying that the
55654      * element should move iTickSize pixels at a time.
55655      */
55656     setXConstraint: function(iLeft, iRight, iTickSize) {
55657         this.leftConstraint = iLeft;
55658         this.rightConstraint = iRight;
55659
55660         this.minX = this.initPageX - iLeft;
55661         this.maxX = this.initPageX + iRight;
55662         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
55663
55664         this.constrainX = true;
55665     },
55666
55667     /**
55668      * Clears any constraints applied to this instance.  Also clears ticks
55669      * since they can't exist independent of a constraint at this time.
55670      */
55671     clearConstraints: function() {
55672         this.constrainX = false;
55673         this.constrainY = false;
55674         this.clearTicks();
55675     },
55676
55677     /**
55678      * Clears any tick interval defined for this instance
55679      */
55680     clearTicks: function() {
55681         this.xTicks = null;
55682         this.yTicks = null;
55683         this.xTickSize = 0;
55684         this.yTickSize = 0;
55685     },
55686
55687     /**
55688      * By default, the element can be dragged any place on the screen.  Set
55689      * this to limit the vertical travel of the element.  Pass in 0,0 for the
55690      * parameters if you want to lock the drag to the x axis.
55691      * @param {Number} iUp the number of pixels the element can move up
55692      * @param {Number} iDown the number of pixels the element can move down
55693      * @param {Number} iTickSize (optional) parameter for specifying that the
55694      * element should move iTickSize pixels at a time.
55695      */
55696     setYConstraint: function(iUp, iDown, iTickSize) {
55697         this.topConstraint = iUp;
55698         this.bottomConstraint = iDown;
55699
55700         this.minY = this.initPageY - iUp;
55701         this.maxY = this.initPageY + iDown;
55702         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
55703
55704         this.constrainY = true;
55705
55706     },
55707
55708     /**
55709      * Must be called if you manually reposition a dd element.
55710      * @param {Boolean} maintainOffset
55711      */
55712     resetConstraints: function() {
55713         // Maintain offsets if necessary
55714         if (this.initPageX || this.initPageX === 0) {
55715             // figure out how much this thing has moved
55716             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
55717             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
55718
55719             this.setInitPosition(dx, dy);
55720
55721         // This is the first time we have detected the element's position
55722         } else {
55723             this.setInitPosition();
55724         }
55725
55726         if (this.constrainX) {
55727             this.setXConstraint( this.leftConstraint,
55728                                  this.rightConstraint,
55729                                  this.xTickSize        );
55730         }
55731
55732         if (this.constrainY) {
55733             this.setYConstraint( this.topConstraint,
55734                                  this.bottomConstraint,
55735                                  this.yTickSize         );
55736         }
55737     },
55738
55739     /**
55740      * Normally the drag element is moved pixel by pixel, but we can specify
55741      * that it move a number of pixels at a time.  This method resolves the
55742      * location when we have it set up like this.
55743      * @param {Number} val where we want to place the object
55744      * @param {Number[]} tickArray sorted array of valid points
55745      * @return {Number} the closest tick
55746      * @private
55747      */
55748     getTick: function(val, tickArray) {
55749         if (!tickArray) {
55750             // If tick interval is not defined, it is effectively 1 pixel,
55751             // so we return the value passed to us.
55752             return val;
55753         } else if (tickArray[0] >= val) {
55754             // The value is lower than the first tick, so we return the first
55755             // tick.
55756             return tickArray[0];
55757         } else {
55758             for (var i=0, len=tickArray.length; i<len; ++i) {
55759                 var next = i + 1;
55760                 if (tickArray[next] && tickArray[next] >= val) {
55761                     var diff1 = val - tickArray[i];
55762                     var diff2 = tickArray[next] - val;
55763                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
55764                 }
55765             }
55766
55767             // The value is larger than the last tick, so we return the last
55768             // tick.
55769             return tickArray[tickArray.length - 1];
55770         }
55771     },
55772
55773     /**
55774      * toString method
55775      * @return {String} string representation of the dd obj
55776      */
55777     toString: function() {
55778         return ("DragDrop " + this.id);
55779     }
55780
55781 });
55782
55783 /*
55784  * This is a derivative of the similarly named class in the YUI Library.
55785  * The original license:
55786  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55787  * Code licensed under the BSD License:
55788  * http://developer.yahoo.net/yui/license.txt
55789  */
55790
55791
55792 /**
55793  * @class Ext.dd.DD
55794  * A DragDrop implementation where the linked element follows the
55795  * mouse cursor during a drag.
55796  * @extends Ext.dd.DragDrop
55797  */
55798 Ext.define('Ext.dd.DD', {
55799     extend: 'Ext.dd.DragDrop',
55800     requires: ['Ext.dd.DragDropManager'],
55801
55802     /**
55803      * Creates new DD instance.
55804      * @param {String} id the id of the linked element
55805      * @param {String} sGroup the group of related DragDrop items
55806      * @param {Object} config an object containing configurable attributes.
55807      * Valid properties for DD: scroll
55808      */
55809     constructor: function(id, sGroup, config) {
55810         if (id) {
55811             this.init(id, sGroup, config);
55812         }
55813     },
55814
55815     /**
55816      * When set to true, the utility automatically tries to scroll the browser
55817      * window when a drag and drop element is dragged near the viewport boundary.
55818      * Defaults to true.
55819      * @property scroll
55820      * @type Boolean
55821      */
55822     scroll: true,
55823
55824     /**
55825      * Sets the pointer offset to the distance between the linked element's top
55826      * left corner and the location the element was clicked
55827      * @method autoOffset
55828      * @param {Number} iPageX the X coordinate of the click
55829      * @param {Number} iPageY the Y coordinate of the click
55830      */
55831     autoOffset: function(iPageX, iPageY) {
55832         var x = iPageX - this.startPageX;
55833         var y = iPageY - this.startPageY;
55834         this.setDelta(x, y);
55835     },
55836
55837     /**
55838      * Sets the pointer offset.  You can call this directly to force the
55839      * offset to be in a particular location (e.g., pass in 0,0 to set it
55840      * to the center of the object)
55841      * @method setDelta
55842      * @param {Number} iDeltaX the distance from the left
55843      * @param {Number} iDeltaY the distance from the top
55844      */
55845     setDelta: function(iDeltaX, iDeltaY) {
55846         this.deltaX = iDeltaX;
55847         this.deltaY = iDeltaY;
55848     },
55849
55850     /**
55851      * Sets the drag element to the location of the mousedown or click event,
55852      * maintaining the cursor location relative to the location on the element
55853      * that was clicked.  Override this if you want to place the element in a
55854      * location other than where the cursor is.
55855      * @method setDragElPos
55856      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55857      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55858      */
55859     setDragElPos: function(iPageX, iPageY) {
55860         // the first time we do this, we are going to check to make sure
55861         // the element has css positioning
55862
55863         var el = this.getDragEl();
55864         this.alignElWithMouse(el, iPageX, iPageY);
55865     },
55866
55867     /**
55868      * Sets the element to the location of the mousedown or click event,
55869      * maintaining the cursor location relative to the location on the element
55870      * that was clicked.  Override this if you want to place the element in a
55871      * location other than where the cursor is.
55872      * @method alignElWithMouse
55873      * @param {HTMLElement} el the element to move
55874      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55875      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55876      */
55877     alignElWithMouse: function(el, iPageX, iPageY) {
55878         var oCoord = this.getTargetCoord(iPageX, iPageY),
55879             fly = el.dom ? el : Ext.fly(el, '_dd'),
55880             elSize = fly.getSize(),
55881             EL = Ext.Element,
55882             vpSize;
55883
55884         if (!this.deltaSetXY) {
55885             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
55886             var aCoord = [
55887                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
55888                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
55889             ];
55890             fly.setXY(aCoord);
55891             var newLeft = fly.getLeft(true);
55892             var newTop  = fly.getTop(true);
55893             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
55894         } else {
55895             vpSize = this.cachedViewportSize;
55896             fly.setLeftTop(
55897                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
55898                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
55899             );
55900         }
55901
55902         this.cachePosition(oCoord.x, oCoord.y);
55903         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
55904         return oCoord;
55905     },
55906
55907     /**
55908      * Saves the most recent position so that we can reset the constraints and
55909      * tick marks on-demand.  We need to know this so that we can calculate the
55910      * number of pixels the element is offset from its original position.
55911      * @method cachePosition
55912      * @param {Number} iPageX (optional) the current x position (this just makes it so we
55913      * don't have to look it up again)
55914      * @param {Number} iPageY (optional) the current y position (this just makes it so we
55915      * don't have to look it up again)
55916      */
55917     cachePosition: function(iPageX, iPageY) {
55918         if (iPageX) {
55919             this.lastPageX = iPageX;
55920             this.lastPageY = iPageY;
55921         } else {
55922             var aCoord = Ext.Element.getXY(this.getEl());
55923             this.lastPageX = aCoord[0];
55924             this.lastPageY = aCoord[1];
55925         }
55926     },
55927
55928     /**
55929      * Auto-scroll the window if the dragged object has been moved beyond the
55930      * visible window boundary.
55931      * @method autoScroll
55932      * @param {Number} x the drag element's x position
55933      * @param {Number} y the drag element's y position
55934      * @param {Number} h the height of the drag element
55935      * @param {Number} w the width of the drag element
55936      * @private
55937      */
55938     autoScroll: function(x, y, h, w) {
55939
55940         if (this.scroll) {
55941             // The client height
55942             var clientH = Ext.Element.getViewHeight();
55943
55944             // The client width
55945             var clientW = Ext.Element.getViewWidth();
55946
55947             // The amt scrolled down
55948             var st = this.DDMInstance.getScrollTop();
55949
55950             // The amt scrolled right
55951             var sl = this.DDMInstance.getScrollLeft();
55952
55953             // Location of the bottom of the element
55954             var bot = h + y;
55955
55956             // Location of the right of the element
55957             var right = w + x;
55958
55959             // The distance from the cursor to the bottom of the visible area,
55960             // adjusted so that we don't scroll if the cursor is beyond the
55961             // element drag constraints
55962             var toBot = (clientH + st - y - this.deltaY);
55963
55964             // The distance from the cursor to the right of the visible area
55965             var toRight = (clientW + sl - x - this.deltaX);
55966
55967
55968             // How close to the edge the cursor must be before we scroll
55969             // var thresh = (document.all) ? 100 : 40;
55970             var thresh = 40;
55971
55972             // How many pixels to scroll per autoscroll op.  This helps to reduce
55973             // clunky scrolling. IE is more sensitive about this ... it needs this
55974             // value to be higher.
55975             var scrAmt = (document.all) ? 80 : 30;
55976
55977             // Scroll down if we are near the bottom of the visible page and the
55978             // obj extends below the crease
55979             if ( bot > clientH && toBot < thresh ) {
55980                 window.scrollTo(sl, st + scrAmt);
55981             }
55982
55983             // Scroll up if the window is scrolled down and the top of the object
55984             // goes above the top border
55985             if ( y < st && st > 0 && y - st < thresh ) {
55986                 window.scrollTo(sl, st - scrAmt);
55987             }
55988
55989             // Scroll right if the obj is beyond the right border and the cursor is
55990             // near the border.
55991             if ( right > clientW && toRight < thresh ) {
55992                 window.scrollTo(sl + scrAmt, st);
55993             }
55994
55995             // Scroll left if the window has been scrolled to the right and the obj
55996             // extends past the left border
55997             if ( x < sl && sl > 0 && x - sl < thresh ) {
55998                 window.scrollTo(sl - scrAmt, st);
55999             }
56000         }
56001     },
56002
56003     /**
56004      * Finds the location the element should be placed if we want to move
56005      * it to where the mouse location less the click offset would place us.
56006      * @method getTargetCoord
56007      * @param {Number} iPageX the X coordinate of the click
56008      * @param {Number} iPageY the Y coordinate of the click
56009      * @return an object that contains the coordinates (Object.x and Object.y)
56010      * @private
56011      */
56012     getTargetCoord: function(iPageX, iPageY) {
56013         var x = iPageX - this.deltaX;
56014         var y = iPageY - this.deltaY;
56015
56016         if (this.constrainX) {
56017             if (x < this.minX) {
56018                 x = this.minX;
56019             }
56020             if (x > this.maxX) {
56021                 x = this.maxX;
56022             }
56023         }
56024
56025         if (this.constrainY) {
56026             if (y < this.minY) {
56027                 y = this.minY;
56028             }
56029             if (y > this.maxY) {
56030                 y = this.maxY;
56031             }
56032         }
56033
56034         x = this.getTick(x, this.xTicks);
56035         y = this.getTick(y, this.yTicks);
56036
56037
56038         return {x: x, y: y};
56039     },
56040
56041     /**
56042      * Sets up config options specific to this class. Overrides
56043      * Ext.dd.DragDrop, but all versions of this method through the
56044      * inheritance chain are called
56045      */
56046     applyConfig: function() {
56047         this.callParent();
56048         this.scroll = (this.config.scroll !== false);
56049     },
56050
56051     /**
56052      * Event that fires prior to the onMouseDown event.  Overrides
56053      * Ext.dd.DragDrop.
56054      */
56055     b4MouseDown: function(e) {
56056         // this.resetConstraints();
56057         this.autoOffset(e.getPageX(), e.getPageY());
56058     },
56059
56060     /**
56061      * Event that fires prior to the onDrag event.  Overrides
56062      * Ext.dd.DragDrop.
56063      */
56064     b4Drag: function(e) {
56065         this.setDragElPos(e.getPageX(), e.getPageY());
56066     },
56067
56068     toString: function() {
56069         return ("DD " + this.id);
56070     }
56071
56072     //////////////////////////////////////////////////////////////////////////
56073     // Debugging ygDragDrop events that can be overridden
56074     //////////////////////////////////////////////////////////////////////////
56075     /*
56076     startDrag: function(x, y) {
56077     },
56078
56079     onDrag: function(e) {
56080     },
56081
56082     onDragEnter: function(e, id) {
56083     },
56084
56085     onDragOver: function(e, id) {
56086     },
56087
56088     onDragOut: function(e, id) {
56089     },
56090
56091     onDragDrop: function(e, id) {
56092     },
56093
56094     endDrag: function(e) {
56095     }
56096
56097     */
56098
56099 });
56100
56101 /*
56102  * This is a derivative of the similarly named class in the YUI Library.
56103  * The original license:
56104  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
56105  * Code licensed under the BSD License:
56106  * http://developer.yahoo.net/yui/license.txt
56107  */
56108
56109 /**
56110  * @class Ext.dd.DDProxy
56111  * @extends Ext.dd.DD
56112  * A DragDrop implementation that inserts an empty, bordered div into
56113  * the document that follows the cursor during drag operations.  At the time of
56114  * the click, the frame div is resized to the dimensions of the linked html
56115  * element, and moved to the exact location of the linked element.
56116  *
56117  * References to the "frame" element refer to the single proxy element that
56118  * was created to be dragged in place of all DDProxy elements on the
56119  * page.
56120  */
56121 Ext.define('Ext.dd.DDProxy', {
56122     extend: 'Ext.dd.DD',
56123
56124     statics: {
56125         /**
56126          * The default drag frame div id
56127          * @static
56128          */
56129         dragElId: "ygddfdiv"
56130     },
56131
56132     /**
56133      * Creates new DDProxy.
56134      * @param {String} id the id of the linked html element
56135      * @param {String} sGroup the group of related DragDrop objects
56136      * @param {Object} config an object containing configurable attributes.
56137      * Valid properties for DDProxy in addition to those in DragDrop:
56138      * 
56139      * - resizeFrame
56140      * - centerFrame
56141      * - dragElId
56142      */
56143     constructor: function(id, sGroup, config) {
56144         if (id) {
56145             this.init(id, sGroup, config);
56146             this.initFrame();
56147         }
56148     },
56149
56150     /**
56151      * By default we resize the drag frame to be the same size as the element
56152      * we want to drag (this is to get the frame effect).  We can turn it off
56153      * if we want a different behavior.
56154      * @property resizeFrame
56155      * @type Boolean
56156      */
56157     resizeFrame: true,
56158
56159     /**
56160      * By default the frame is positioned exactly where the drag element is, so
56161      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
56162      * you do not have constraints on the obj is to have the drag frame centered
56163      * around the cursor.  Set centerFrame to true for this effect.
56164      * @property centerFrame
56165      * @type Boolean
56166      */
56167     centerFrame: false,
56168
56169     /**
56170      * Creates the proxy element if it does not yet exist
56171      * @method createFrame
56172      */
56173     createFrame: function() {
56174         var self = this;
56175         var body = document.body;
56176
56177         if (!body || !body.firstChild) {
56178             setTimeout( function() { self.createFrame(); }, 50 );
56179             return;
56180         }
56181
56182         var div = this.getDragEl();
56183
56184         if (!div) {
56185             div    = document.createElement("div");
56186             div.id = this.dragElId;
56187             var s  = div.style;
56188
56189             s.position   = "absolute";
56190             s.visibility = "hidden";
56191             s.cursor     = "move";
56192             s.border     = "2px solid #aaa";
56193             s.zIndex     = 999;
56194
56195             // appendChild can blow up IE if invoked prior to the window load event
56196             // while rendering a table.  It is possible there are other scenarios
56197             // that would cause this to happen as well.
56198             body.insertBefore(div, body.firstChild);
56199         }
56200     },
56201
56202     /**
56203      * Initialization for the drag frame element.  Must be called in the
56204      * constructor of all subclasses
56205      * @method initFrame
56206      */
56207     initFrame: function() {
56208         this.createFrame();
56209     },
56210
56211     applyConfig: function() {
56212         this.callParent();
56213
56214         this.resizeFrame = (this.config.resizeFrame !== false);
56215         this.centerFrame = (this.config.centerFrame);
56216         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
56217     },
56218
56219     /**
56220      * Resizes the drag frame to the dimensions of the clicked object, positions
56221      * it over the object, and finally displays it
56222      * @method showFrame
56223      * @param {Number} iPageX X click position
56224      * @param {Number} iPageY Y click position
56225      * @private
56226      */
56227     showFrame: function(iPageX, iPageY) {
56228         var el = this.getEl();
56229         var dragEl = this.getDragEl();
56230         var s = dragEl.style;
56231
56232         this._resizeProxy();
56233
56234         if (this.centerFrame) {
56235             this.setDelta( Math.round(parseInt(s.width,  10)/2),
56236                            Math.round(parseInt(s.height, 10)/2) );
56237         }
56238
56239         this.setDragElPos(iPageX, iPageY);
56240
56241         Ext.fly(dragEl).show();
56242     },
56243
56244     /**
56245      * The proxy is automatically resized to the dimensions of the linked
56246      * element when a drag is initiated, unless resizeFrame is set to false
56247      * @method _resizeProxy
56248      * @private
56249      */
56250     _resizeProxy: function() {
56251         if (this.resizeFrame) {
56252             var el = this.getEl();
56253             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
56254         }
56255     },
56256
56257     // overrides Ext.dd.DragDrop
56258     b4MouseDown: function(e) {
56259         var x = e.getPageX();
56260         var y = e.getPageY();
56261         this.autoOffset(x, y);
56262         this.setDragElPos(x, y);
56263     },
56264
56265     // overrides Ext.dd.DragDrop
56266     b4StartDrag: function(x, y) {
56267         // show the drag frame
56268         this.showFrame(x, y);
56269     },
56270
56271     // overrides Ext.dd.DragDrop
56272     b4EndDrag: function(e) {
56273         Ext.fly(this.getDragEl()).hide();
56274     },
56275
56276     // overrides Ext.dd.DragDrop
56277     // By default we try to move the element to the last location of the frame.
56278     // This is so that the default behavior mirrors that of Ext.dd.DD.
56279     endDrag: function(e) {
56280
56281         var lel = this.getEl();
56282         var del = this.getDragEl();
56283
56284         // Show the drag frame briefly so we can get its position
56285         del.style.visibility = "";
56286
56287         this.beforeMove();
56288         // Hide the linked element before the move to get around a Safari
56289         // rendering bug.
56290         lel.style.visibility = "hidden";
56291         Ext.dd.DDM.moveToEl(lel, del);
56292         del.style.visibility = "hidden";
56293         lel.style.visibility = "";
56294
56295         this.afterDrag();
56296     },
56297
56298     beforeMove : function(){
56299
56300     },
56301
56302     afterDrag : function(){
56303
56304     },
56305
56306     toString: function() {
56307         return ("DDProxy " + this.id);
56308     }
56309
56310 });
56311
56312 /**
56313  * @class Ext.dd.DragSource
56314  * @extends Ext.dd.DDProxy
56315  * A simple class that provides the basic implementation needed to make any element draggable.
56316  */
56317 Ext.define('Ext.dd.DragSource', {
56318     extend: 'Ext.dd.DDProxy',
56319     requires: [
56320         'Ext.dd.StatusProxy',
56321         'Ext.dd.DragDropManager'
56322     ],
56323
56324     /**
56325      * @cfg {String} ddGroup
56326      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
56327      * interact with other drag drop objects in the same group.
56328      */
56329
56330     /**
56331      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
56332      * The CSS class returned to the drag source when drop is allowed.
56333      */
56334     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
56335     /**
56336      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
56337      * The CSS class returned to the drag source when drop is not allowed.
56338      */
56339     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
56340
56341     /**
56342      * @cfg {Boolean} animRepair
56343      * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
56344      */
56345     animRepair: true,
56346
56347     /**
56348      * @cfg {String} repairHighlightColor
56349      * The color to use when visually highlighting the drag source in the afterRepair
56350      * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
56351      * a preceding '#'.
56352      */
56353     repairHighlightColor: 'c3daf9',
56354
56355     /**
56356      * Creates new drag-source.
56357      * @constructor
56358      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
56359      * @param {Object} config (optional) Config object.
56360      */
56361     constructor: function(el, config) {
56362         this.el = Ext.get(el);
56363         if(!this.dragData){
56364             this.dragData = {};
56365         }
56366
56367         Ext.apply(this, config);
56368
56369         if(!this.proxy){
56370             this.proxy = Ext.create('Ext.dd.StatusProxy', {
56371                 animRepair: this.animRepair
56372             });
56373         }
56374         this.callParent([this.el.dom, this.ddGroup || this.group,
56375               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
56376
56377         this.dragging = false;
56378     },
56379
56380     /**
56381      * Returns the data object associated with this drag source
56382      * @return {Object} data An object containing arbitrary data
56383      */
56384     getDragData : function(e){
56385         return this.dragData;
56386     },
56387
56388     // private
56389     onDragEnter : function(e, id){
56390         var target = Ext.dd.DragDropManager.getDDById(id);
56391         this.cachedTarget = target;
56392         if (this.beforeDragEnter(target, e, id) !== false) {
56393             if (target.isNotifyTarget) {
56394                 var status = target.notifyEnter(this, e, this.dragData);
56395                 this.proxy.setStatus(status);
56396             } else {
56397                 this.proxy.setStatus(this.dropAllowed);
56398             }
56399
56400             if (this.afterDragEnter) {
56401                 /**
56402                  * An empty function by default, but provided so that you can perform a custom action
56403                  * when the dragged item enters the drop target by providing an implementation.
56404                  * @param {Ext.dd.DragDrop} target The drop target
56405                  * @param {Event} e The event object
56406                  * @param {String} id The id of the dragged element
56407                  * @method afterDragEnter
56408                  */
56409                 this.afterDragEnter(target, e, id);
56410             }
56411         }
56412     },
56413
56414     /**
56415      * An empty function by default, but provided so that you can perform a custom action
56416      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
56417      * @param {Ext.dd.DragDrop} target The drop target
56418      * @param {Event} e The event object
56419      * @param {String} id The id of the dragged element
56420      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56421      */
56422     beforeDragEnter: function(target, e, id) {
56423         return true;
56424     },
56425
56426     // private
56427     alignElWithMouse: function() {
56428         this.callParent(arguments);
56429         this.proxy.sync();
56430     },
56431
56432     // private
56433     onDragOver: function(e, id) {
56434         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
56435         if (this.beforeDragOver(target, e, id) !== false) {
56436             if(target.isNotifyTarget){
56437                 var status = target.notifyOver(this, e, this.dragData);
56438                 this.proxy.setStatus(status);
56439             }
56440
56441             if (this.afterDragOver) {
56442                 /**
56443                  * An empty function by default, but provided so that you can perform a custom action
56444                  * while the dragged item is over the drop target by providing an implementation.
56445                  * @param {Ext.dd.DragDrop} target The drop target
56446                  * @param {Event} e The event object
56447                  * @param {String} id The id of the dragged element
56448                  * @method afterDragOver
56449                  */
56450                 this.afterDragOver(target, e, id);
56451             }
56452         }
56453     },
56454
56455     /**
56456      * An empty function by default, but provided so that you can perform a custom action
56457      * while the dragged item is over the drop target and optionally cancel the onDragOver.
56458      * @param {Ext.dd.DragDrop} target The drop target
56459      * @param {Event} e The event object
56460      * @param {String} id The id of the dragged element
56461      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56462      */
56463     beforeDragOver: function(target, e, id) {
56464         return true;
56465     },
56466
56467     // private
56468     onDragOut: function(e, id) {
56469         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
56470         if (this.beforeDragOut(target, e, id) !== false) {
56471             if (target.isNotifyTarget) {
56472                 target.notifyOut(this, e, this.dragData);
56473             }
56474             this.proxy.reset();
56475             if (this.afterDragOut) {
56476                 /**
56477                  * An empty function by default, but provided so that you can perform a custom action
56478                  * after the dragged item is dragged out of the target without dropping.
56479                  * @param {Ext.dd.DragDrop} target The drop target
56480                  * @param {Event} e The event object
56481                  * @param {String} id The id of the dragged element
56482                  * @method afterDragOut
56483                  */
56484                 this.afterDragOut(target, e, id);
56485             }
56486         }
56487         this.cachedTarget = null;
56488     },
56489
56490     /**
56491      * An empty function by default, but provided so that you can perform a custom action before the dragged
56492      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
56493      * @param {Ext.dd.DragDrop} target The drop target
56494      * @param {Event} e The event object
56495      * @param {String} id The id of the dragged element
56496      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56497      */
56498     beforeDragOut: function(target, e, id){
56499         return true;
56500     },
56501
56502     // private
56503     onDragDrop: function(e, id){
56504         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
56505         if (this.beforeDragDrop(target, e, id) !== false) {
56506             if (target.isNotifyTarget) {
56507                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
56508                     this.onValidDrop(target, e, id);
56509                 } else {
56510                     this.onInvalidDrop(target, e, id);
56511                 }
56512             } else {
56513                 this.onValidDrop(target, e, id);
56514             }
56515
56516             if (this.afterDragDrop) {
56517                 /**
56518                  * An empty function by default, but provided so that you can perform a custom action
56519                  * after a valid drag drop has occurred by providing an implementation.
56520                  * @param {Ext.dd.DragDrop} target The drop target
56521                  * @param {Event} e The event object
56522                  * @param {String} id The id of the dropped element
56523                  * @method afterDragDrop
56524                  */
56525                 this.afterDragDrop(target, e, id);
56526             }
56527         }
56528         delete this.cachedTarget;
56529     },
56530
56531     /**
56532      * An empty function by default, but provided so that you can perform a custom action before the dragged
56533      * item is dropped onto the target and optionally cancel the onDragDrop.
56534      * @param {Ext.dd.DragDrop} target The drop target
56535      * @param {Event} e The event object
56536      * @param {String} id The id of the dragged element
56537      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
56538      */
56539     beforeDragDrop: function(target, e, id){
56540         return true;
56541     },
56542
56543     // private
56544     onValidDrop: function(target, e, id){
56545         this.hideProxy();
56546         if(this.afterValidDrop){
56547             /**
56548              * An empty function by default, but provided so that you can perform a custom action
56549              * after a valid drop has occurred by providing an implementation.
56550              * @param {Object} target The target DD
56551              * @param {Event} e The event object
56552              * @param {String} id The id of the dropped element
56553              * @method afterValidDrop
56554              */
56555             this.afterValidDrop(target, e, id);
56556         }
56557     },
56558
56559     // private
56560     getRepairXY: function(e, data){
56561         return this.el.getXY();
56562     },
56563
56564     // private
56565     onInvalidDrop: function(target, e, id) {
56566         this.beforeInvalidDrop(target, e, id);
56567         if (this.cachedTarget) {
56568             if(this.cachedTarget.isNotifyTarget){
56569                 this.cachedTarget.notifyOut(this, e, this.dragData);
56570             }
56571             this.cacheTarget = null;
56572         }
56573         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
56574
56575         if (this.afterInvalidDrop) {
56576             /**
56577              * An empty function by default, but provided so that you can perform a custom action
56578              * after an invalid drop has occurred by providing an implementation.
56579              * @param {Event} e The event object
56580              * @param {String} id The id of the dropped element
56581              * @method afterInvalidDrop
56582              */
56583             this.afterInvalidDrop(e, id);
56584         }
56585     },
56586
56587     // private
56588     afterRepair: function() {
56589         var me = this;
56590         if (Ext.enableFx) {
56591             me.el.highlight(me.repairHighlightColor);
56592         }
56593         me.dragging = false;
56594     },
56595
56596     /**
56597      * An empty function by default, but provided so that you can perform a custom action after an invalid
56598      * drop has occurred.
56599      * @param {Ext.dd.DragDrop} target The drop target
56600      * @param {Event} e The event object
56601      * @param {String} id The id of the dragged element
56602      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
56603      */
56604     beforeInvalidDrop: function(target, e, id) {
56605         return true;
56606     },
56607
56608     // private
56609     handleMouseDown: function(e) {
56610         if (this.dragging) {
56611             return;
56612         }
56613         var data = this.getDragData(e);
56614         if (data && this.onBeforeDrag(data, e) !== false) {
56615             this.dragData = data;
56616             this.proxy.stop();
56617             this.callParent(arguments);
56618         }
56619     },
56620
56621     /**
56622      * An empty function by default, but provided so that you can perform a custom action before the initial
56623      * drag event begins and optionally cancel it.
56624      * @param {Object} data An object containing arbitrary data to be shared with drop targets
56625      * @param {Event} e The event object
56626      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
56627      */
56628     onBeforeDrag: function(data, e){
56629         return true;
56630     },
56631
56632     /**
56633      * An empty function by default, but provided so that you can perform a custom action once the initial
56634      * drag event has begun.  The drag cannot be canceled from this function.
56635      * @param {Number} x The x position of the click on the dragged object
56636      * @param {Number} y The y position of the click on the dragged object
56637      * @method
56638      */
56639     onStartDrag: Ext.emptyFn,
56640
56641     // private override
56642     startDrag: function(x, y) {
56643         this.proxy.reset();
56644         this.dragging = true;
56645         this.proxy.update("");
56646         this.onInitDrag(x, y);
56647         this.proxy.show();
56648     },
56649
56650     // private
56651     onInitDrag: function(x, y) {
56652         var clone = this.el.dom.cloneNode(true);
56653         clone.id = Ext.id(); // prevent duplicate ids
56654         this.proxy.update(clone);
56655         this.onStartDrag(x, y);
56656         return true;
56657     },
56658
56659     /**
56660      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
56661      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
56662      */
56663     getProxy: function() {
56664         return this.proxy;
56665     },
56666
56667     /**
56668      * Hides the drag source's {@link Ext.dd.StatusProxy}
56669      */
56670     hideProxy: function() {
56671         this.proxy.hide();
56672         this.proxy.reset(true);
56673         this.dragging = false;
56674     },
56675
56676     // private
56677     triggerCacheRefresh: function() {
56678         Ext.dd.DDM.refreshCache(this.groups);
56679     },
56680
56681     // private - override to prevent hiding
56682     b4EndDrag: function(e) {
56683     },
56684
56685     // private - override to prevent moving
56686     endDrag : function(e){
56687         this.onEndDrag(this.dragData, e);
56688     },
56689
56690     // private
56691     onEndDrag : function(data, e){
56692     },
56693
56694     // private - pin to cursor
56695     autoOffset : function(x, y) {
56696         this.setDelta(-12, -20);
56697     },
56698
56699     destroy: function(){
56700         this.callParent();
56701         Ext.destroy(this.proxy);
56702     }
56703 });
56704
56705 // private - DD implementation for Panels
56706 Ext.define('Ext.panel.DD', {
56707     extend: 'Ext.dd.DragSource',
56708     requires: ['Ext.panel.Proxy'],
56709
56710     constructor : function(panel, cfg){
56711         this.panel = panel;
56712         this.dragData = {panel: panel};
56713         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
56714
56715         this.callParent([panel.el, cfg]);
56716
56717         Ext.defer(function() {
56718             var header = panel.header,
56719                 el = panel.body;
56720
56721             if(header){
56722                 this.setHandleElId(header.id);
56723                 el = header.el;
56724             }
56725             el.setStyle('cursor', 'move');
56726             this.scroll = false;
56727         }, 200, this);
56728     },
56729
56730     showFrame: Ext.emptyFn,
56731     startDrag: Ext.emptyFn,
56732     b4StartDrag: function(x, y) {
56733         this.proxy.show();
56734     },
56735     b4MouseDown: function(e) {
56736         var x = e.getPageX(),
56737             y = e.getPageY();
56738         this.autoOffset(x, y);
56739     },
56740     onInitDrag : function(x, y){
56741         this.onStartDrag(x, y);
56742         return true;
56743     },
56744     createFrame : Ext.emptyFn,
56745     getDragEl : function(e){
56746         return this.proxy.ghost.el.dom;
56747     },
56748     endDrag : function(e){
56749         this.proxy.hide();
56750         this.panel.saveState();
56751     },
56752
56753     autoOffset : function(x, y) {
56754         x -= this.startPageX;
56755         y -= this.startPageY;
56756         this.setDelta(x, y);
56757     }
56758 });
56759
56760 /**
56761  * @class Ext.layout.component.Dock
56762  * @extends Ext.layout.component.AbstractDock
56763  * @private
56764  */
56765 Ext.define('Ext.layout.component.Dock', {
56766
56767     /* Begin Definitions */
56768
56769     alias: ['layout.dock'],
56770
56771     extend: 'Ext.layout.component.AbstractDock'
56772
56773     /* End Definitions */
56774
56775 });
56776 /**
56777  * Panel is a container that has specific functionality and structural components that make it the perfect building
56778  * block for application-oriented user interfaces.
56779  *
56780  * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
56781  * {@link Ext.container.Container#layout layout}, and containing child Components.
56782  *
56783  * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
56784  * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
56785  * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
56786  * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
56787  * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
56788  *
56789  * {@img Ext.panel.Panel/panel.png Panel components}
56790  *
56791  * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
56792  * Ext.panel.Header header}, {@link #fbar footer} and body sections.
56793  *
56794  * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
56795  * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
56796  * is {@link Ext.container.Container#add completely managed by the framework}.
56797  *
56798  * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
56799  * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
56800  * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
56801  * `{@link #closeAction closeAction}: 'hide'`.
56802  *
56803  * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
56804  * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
56805  * Panel into the document, here's how to do it:
56806  *
56807  *     @example
56808  *     Ext.create('Ext.panel.Panel', {
56809  *         title: 'Hello',
56810  *         width: 200,
56811  *         html: '<p>World!</p>',
56812  *         renderTo: Ext.getBody()
56813  *     });
56814  *
56815  * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
56816  * constituent part of a Container:
56817  *
56818  *     @example
56819  *     var filterPanel = Ext.create('Ext.panel.Panel', {
56820  *         bodyPadding: 5,  // Don't want content to crunch against the borders
56821  *         width: 300,
56822  *         title: 'Filters',
56823  *         items: [{
56824  *             xtype: 'datefield',
56825  *             fieldLabel: 'Start date'
56826  *         }, {
56827  *             xtype: 'datefield',
56828  *             fieldLabel: 'End date'
56829  *         }],
56830  *         renderTo: Ext.getBody()
56831  *     });
56832  *
56833  * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
56834  * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
56835  * size and position its child Components.
56836  *
56837  * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
56838  * arranging child Components:
56839  *
56840  *     @example
56841  *     var resultsPanel = Ext.create('Ext.panel.Panel', {
56842  *         title: 'Results',
56843  *         width: 600,
56844  *         height: 400,
56845  *         renderTo: Ext.getBody(),
56846  *         layout: {
56847  *             type: 'vbox',       // Arrange child items vertically
56848  *             align: 'stretch',    // Each takes up full width
56849  *             padding: 5
56850  *         },
56851  *         items: [{               // Results grid specified as a config object with an xtype of 'grid'
56852  *             xtype: 'grid',
56853  *             columns: [{header: 'Column One'}],            // One header just for show. There's no data,
56854  *             store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
56855  *             flex: 1                                       // Use 1/3 of Container's height (hint to Box layout)
56856  *         }, {
56857  *             xtype: 'splitter'   // A splitter between the two child items
56858  *         }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
56859  *             title: 'Details',
56860  *             bodyPadding: 5,
56861  *             items: [{
56862  *                 fieldLabel: 'Data item',
56863  *                 xtype: 'textfield'
56864  *             }], // An array of form fields
56865  *             flex: 2             // Use 2/3 of Container's height (hint to Box layout)
56866  *         }]
56867  *     });
56868  *
56869  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
56870  * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
56871  * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
56872  * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
56873  * distribute available space proportionately.
56874  *
56875  * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
56876  * within its content area.
56877  *
56878  * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
56879  * level of nested containment, all dynamically sized according to configuration, the user's preference and available
56880  * browser size.
56881  */
56882 Ext.define('Ext.panel.Panel', {
56883     extend: 'Ext.panel.AbstractPanel',
56884     requires: [
56885         'Ext.panel.Header',
56886         'Ext.fx.Anim',
56887         'Ext.util.KeyMap',
56888         'Ext.panel.DD',
56889         'Ext.XTemplate',
56890         'Ext.layout.component.Dock',
56891         'Ext.util.Memento'
56892     ],
56893     alias: 'widget.panel',
56894     alternateClassName: 'Ext.Panel',
56895
56896     /**
56897      * @cfg {String} collapsedCls
56898      * A CSS class to add to the panel's element after it has been collapsed.
56899      */
56900     collapsedCls: 'collapsed',
56901
56902     /**
56903      * @cfg {Boolean} animCollapse
56904      * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
56905      * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
56906      * duration in milliseconds.
56907      */
56908     animCollapse: Ext.enableFx,
56909
56910     /**
56911      * @cfg {Number} minButtonWidth
56912      * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
56913      * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
56914      * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
56915      * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
56916      * their parent container.
56917      */
56918     minButtonWidth: 75,
56919
56920     /**
56921      * @cfg {Boolean} collapsed
56922      * `true` to render the panel collapsed, `false` to render it expanded.
56923      */
56924     collapsed: false,
56925
56926     /**
56927      * @cfg {Boolean} collapseFirst
56928      * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
56929      * the panel's title bar, `false` to render it last.
56930      */
56931     collapseFirst: true,
56932
56933     /**
56934      * @cfg {Boolean} hideCollapseTool
56935      * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
56936      */
56937     hideCollapseTool: false,
56938
56939     /**
56940      * @cfg {Boolean} titleCollapse
56941      * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
56942      * the header bar, `false`) to allow it only by clicking to tool butto).
56943      */
56944     titleCollapse: false,
56945
56946     /**
56947      * @cfg {String} collapseMode
56948      * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
56949      * {@link Ext.layout.container.Border border layout}.**
56950      *
56951      * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
56952      * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
56953      * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
56954      * expand tool.
56955      *
56956      * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
56957      *
56958      * - **`undefined/omitted`**
56959      *
56960      *   When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
56961      *   and to provide a UI with a Tool to allow the user to re-expand the Panel.
56962      *
56963      * - **`header`** :
56964      *
56965      *   The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
56966      *   layout}.
56967      */
56968
56969     /**
56970      * @cfg {Ext.Component/Object} placeholder
56971      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56972      * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
56973      *
56974      * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
56975      * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
56976      * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
56977      */
56978
56979     /**
56980      * @cfg {Boolean} floatable
56981      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56982      * {@link Ext.layout.container.Border border layout}.**
56983      *
56984      * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
56985      * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
56986      */
56987     floatable: true,
56988
56989     /**
56990      * @cfg {Boolean} overlapHeader
56991      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
56992      * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
56993      * which does not have frame:true, this will need to be set to true.
56994      */
56995
56996     /**
56997      * @cfg {Boolean} collapsible
56998      * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
56999      * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
57000      *
57001      * See {@link #collapseMode} and {@link #collapseDirection}
57002      */
57003     collapsible: false,
57004
57005     /**
57006      * @cfg {Boolean} collapseDirection
57007      * The direction to collapse the Panel when the toggle button is clicked.
57008      *
57009      * Defaults to the {@link #headerPosition}
57010      *
57011      * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
57012      * Ext.layout.container.Border border layout}.**
57013      *
57014      * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
57015      */
57016
57017     /**
57018      * @cfg {Boolean} closable
57019      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
57020      * disallow closing the window.
57021      *
57022      * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
57023      * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
57024      * reused.
57025      *
57026      * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
57027      */
57028     closable: false,
57029
57030     /**
57031      * @cfg {String} closeAction
57032      * The action to take when the close header tool is clicked:
57033      *
57034      * - **`'{@link #destroy}'`** :
57035      *
57036      *   {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
57037      *   Components. The window will **not** be available to be redisplayed via the {@link #show} method.
57038      *
57039      * - **`'{@link #hide}'`** :
57040      *
57041      *   {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
57042      *   available to be redisplayed via the {@link #show} method.
57043      *
57044      * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
57045      * approriate closeAction.
57046      */
57047     closeAction: 'destroy',
57048
57049     /**
57050      * @cfg {Object/Object[]} dockedItems
57051      * A component or series of components to be added as docked items to this panel. The docked items can be docked to
57052      * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
57053      *
57054      *     var panel = new Ext.panel.Panel({
57055      *         dockedItems: [{
57056      *             xtype: 'toolbar',
57057      *             dock: 'top',
57058      *             items: [{
57059      *                 text: 'Docked to the top'
57060      *             }]
57061      *         }]
57062      *     });
57063      */
57064
57065     /**
57066       * @cfg {Boolean} preventHeader
57067       * Prevent a Header from being created and shown.
57068       */
57069     preventHeader: false,
57070
57071      /**
57072       * @cfg {String} headerPosition
57073       * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
57074       */
57075     headerPosition: 'top',
57076
57077      /**
57078      * @cfg {Boolean} frame
57079      * True to apply a frame to the panel.
57080      */
57081     frame: false,
57082
57083     /**
57084      * @cfg {Boolean} frameHeader
57085      * True to apply a frame to the panel panels header (if 'frame' is true).
57086      */
57087     frameHeader: true,
57088
57089     /**
57090      * @cfg {Object[]/Ext.panel.Tool[]} tools
57091      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
57092      * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
57093      * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
57094      *
57095      * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
57096      * visual button. Any required functionality must be provided by adding handlers that implement the necessary
57097      * behavior.
57098      *
57099      * Example usage:
57100      *
57101      *     tools:[{
57102      *         type:'refresh',
57103      *         tooltip: 'Refresh form Data',
57104      *         // hidden:true,
57105      *         handler: function(event, toolEl, panel){
57106      *             // refresh logic
57107      *         }
57108      *     },
57109      *     {
57110      *         type:'help',
57111      *         tooltip: 'Get Help',
57112      *         handler: function(event, toolEl, panel){
57113      *             // show help here
57114      *         }
57115      *     }]
57116      */
57117
57118     /**
57119      * @cfg {String} [title='']
57120      * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
57121      * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
57122      * {@link #preventHeader} is set to `true`.
57123      */
57124
57125     /**
57126      * @cfg {String} iconCls
57127      * CSS class for icon in header. Used for displaying an icon to the left of a title.
57128      */
57129
57130     initComponent: function() {
57131         var me = this,
57132             cls;
57133
57134         me.addEvents(
57135
57136             /**
57137              * @event beforeclose
57138              * Fires before the user closes the panel. Return false from any listener to stop the close event being
57139              * fired
57140              * @param {Ext.panel.Panel} panel The Panel object
57141              */
57142             'beforeclose',
57143
57144             /**
57145              * @event beforeexpand
57146              * Fires before this panel is expanded. Return false to prevent the expand.
57147              * @param {Ext.panel.Panel} p The Panel being expanded.
57148              * @param {Boolean} animate True if the expand is animated, else false.
57149              */
57150             "beforeexpand",
57151
57152             /**
57153              * @event beforecollapse
57154              * Fires before this panel is collapsed. Return false to prevent the collapse.
57155              * @param {Ext.panel.Panel} p The Panel being collapsed.
57156              * @param {String} direction . The direction of the collapse. One of
57157              *
57158              *   - Ext.Component.DIRECTION_TOP
57159              *   - Ext.Component.DIRECTION_RIGHT
57160              *   - Ext.Component.DIRECTION_BOTTOM
57161              *   - Ext.Component.DIRECTION_LEFT
57162              *
57163              * @param {Boolean} animate True if the collapse is animated, else false.
57164              */
57165             "beforecollapse",
57166
57167             /**
57168              * @event expand
57169              * Fires after this Panel has expanded.
57170              * @param {Ext.panel.Panel} p The Panel that has been expanded.
57171              */
57172             "expand",
57173
57174             /**
57175              * @event collapse
57176              * Fires after this Panel hass collapsed.
57177              * @param {Ext.panel.Panel} p The Panel that has been collapsed.
57178              */
57179             "collapse",
57180
57181             /**
57182              * @event titlechange
57183              * Fires after the Panel title has been set or changed.
57184              * @param {Ext.panel.Panel} p the Panel which has been resized.
57185              * @param {String} newTitle The new title.
57186              * @param {String} oldTitle The previous panel title.
57187              */
57188             'titlechange',
57189
57190             /**
57191              * @event iconchange
57192              * Fires after the Panel iconCls has been set or changed.
57193              * @param {Ext.panel.Panel} p the Panel which has been resized.
57194              * @param {String} newIconCls The new iconCls.
57195              * @param {String} oldIconCls The previous panel iconCls.
57196              */
57197             'iconchange'
57198         );
57199
57200         // Save state on these two events.
57201         this.addStateEvents('expand', 'collapse');
57202
57203         if (me.unstyled) {
57204             me.setUI('plain');
57205         }
57206
57207         if (me.frame) {
57208             me.setUI(me.ui + '-framed');
57209         }
57210
57211         // Backwards compatibility
57212         me.bridgeToolbars();
57213
57214         me.callParent();
57215         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
57216     },
57217
57218     setBorder: function(border) {
57219         // var me     = this,
57220         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
57221         //
57222         // me.callParent(arguments);
57223         //
57224         // if (me.collapsed) {
57225         //     me[method](me.collapsedCls + '-noborder');
57226         // }
57227         //
57228         // if (me.header) {
57229         //     me.header.setBorder(border);
57230         //     if (me.collapsed) {
57231         //         me.header[method](me.collapsedCls + '-noborder');
57232         //     }
57233         // }
57234
57235         this.callParent(arguments);
57236     },
57237
57238     beforeDestroy: function() {
57239         Ext.destroy(
57240             this.ghostPanel,
57241             this.dd
57242         );
57243         this.callParent();
57244     },
57245
57246     initAria: function() {
57247         this.callParent();
57248         this.initHeaderAria();
57249     },
57250
57251     initHeaderAria: function() {
57252         var me = this,
57253             el = me.el,
57254             header = me.header;
57255         if (el && header) {
57256             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
57257         }
57258     },
57259
57260     getHeader: function() {
57261         return this.header;
57262     },
57263
57264     /**
57265      * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
57266      * @param {String} newTitle
57267      */
57268     setTitle: function(newTitle) {
57269         var me = this,
57270         oldTitle = this.title;
57271
57272         me.title = newTitle;
57273         if (me.header) {
57274             me.header.setTitle(newTitle);
57275         } else {
57276             me.updateHeader();
57277         }
57278
57279         if (me.reExpander) {
57280             me.reExpander.setTitle(newTitle);
57281         }
57282         me.fireEvent('titlechange', me, newTitle, oldTitle);
57283     },
57284
57285     /**
57286      * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
57287      * {@link #iconchange} event after completion.
57288      * @param {String} newIconCls The new CSS class name
57289      */
57290     setIconCls: function(newIconCls) {
57291         var me = this,
57292             oldIconCls = me.iconCls;
57293
57294         me.iconCls = newIconCls;
57295         var header = me.header;
57296         if (header) {
57297             header.setIconCls(newIconCls);
57298         }
57299         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
57300     },
57301
57302     bridgeToolbars: function() {
57303         var me = this,
57304             docked = [],
57305             fbar,
57306             fbarDefaults,
57307             minButtonWidth = me.minButtonWidth;
57308
57309         function initToolbar (toolbar, pos, useButtonAlign) {
57310             if (Ext.isArray(toolbar)) {
57311                 toolbar = {
57312                     xtype: 'toolbar',
57313                     items: toolbar
57314                 };
57315             }
57316             else if (!toolbar.xtype) {
57317                 toolbar.xtype = 'toolbar';
57318             }
57319             toolbar.dock = pos;
57320             if (pos == 'left' || pos == 'right') {
57321                 toolbar.vertical = true;
57322             }
57323
57324             // Legacy support for buttonAlign (only used by buttons/fbar)
57325             if (useButtonAlign) {
57326                 toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
57327                     // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
57328                     pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
57329                 });
57330             }
57331             return toolbar;
57332         }
57333
57334         // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
57335
57336         /**
57337          * @cfg {String} buttonAlign
57338          * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
57339          * 'right' for buttons/fbar, 'left' for other toolbar types).
57340          *
57341          * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
57342          * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
57343          */
57344
57345         /**
57346          * @cfg {Object/Object[]} tbar
57347          * Convenience config. Short for 'Top Bar'.
57348          *
57349          *     tbar: [
57350          *       { xtype: 'button', text: 'Button 1' }
57351          *     ]
57352          *
57353          * is equivalent to
57354          *
57355          *     dockedItems: [{
57356          *         xtype: 'toolbar',
57357          *         dock: 'top',
57358          *         items: [
57359          *             { xtype: 'button', text: 'Button 1' }
57360          *         ]
57361          *     }]
57362          */
57363         if (me.tbar) {
57364             docked.push(initToolbar(me.tbar, 'top'));
57365             me.tbar = null;
57366         }
57367
57368         /**
57369          * @cfg {Object/Object[]} bbar
57370          * Convenience config. Short for 'Bottom Bar'.
57371          *
57372          *     bbar: [
57373          *       { xtype: 'button', text: 'Button 1' }
57374          *     ]
57375          *
57376          * is equivalent to
57377          *
57378          *     dockedItems: [{
57379          *         xtype: 'toolbar',
57380          *         dock: 'bottom',
57381          *         items: [
57382          *             { xtype: 'button', text: 'Button 1' }
57383          *         ]
57384          *     }]
57385          */
57386         if (me.bbar) {
57387             docked.push(initToolbar(me.bbar, 'bottom'));
57388             me.bbar = null;
57389         }
57390
57391         /**
57392          * @cfg {Object/Object[]} buttons
57393          * Convenience config used for adding buttons docked to the bottom of the panel. This is a
57394          * synonym for the {@link #fbar} config.
57395          *
57396          *     buttons: [
57397          *       { text: 'Button 1' }
57398          *     ]
57399          *
57400          * is equivalent to
57401          *
57402          *     dockedItems: [{
57403          *         xtype: 'toolbar',
57404          *         dock: 'bottom',
57405          *         ui: 'footer',
57406          *         defaults: {minWidth: {@link #minButtonWidth}},
57407          *         items: [
57408          *             { xtype: 'component', flex: 1 },
57409          *             { xtype: 'button', text: 'Button 1' }
57410          *         ]
57411          *     }]
57412          *
57413          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
57414          * each of the buttons in the buttons toolbar.
57415          */
57416         if (me.buttons) {
57417             me.fbar = me.buttons;
57418             me.buttons = null;
57419         }
57420
57421         /**
57422          * @cfg {Object/Object[]} fbar
57423          * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
57424          *
57425          *     fbar: [
57426          *       { type: 'button', text: 'Button 1' }
57427          *     ]
57428          *
57429          * is equivalent to
57430          *
57431          *     dockedItems: [{
57432          *         xtype: 'toolbar',
57433          *         dock: 'bottom',
57434          *         ui: 'footer',
57435          *         defaults: {minWidth: {@link #minButtonWidth}},
57436          *         items: [
57437          *             { xtype: 'component', flex: 1 },
57438          *             { xtype: 'button', text: 'Button 1' }
57439          *         ]
57440          *     }]
57441          *
57442          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
57443          * each of the buttons in the fbar.
57444          */
57445         if (me.fbar) {
57446             fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
57447             fbar.ui = 'footer';
57448
57449             // Apply the minButtonWidth config to buttons in the toolbar
57450             if (minButtonWidth) {
57451                 fbarDefaults = fbar.defaults;
57452                 fbar.defaults = function(config) {
57453                     var defaults = fbarDefaults || {};
57454                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
57455                             !('minWidth' in defaults)) {
57456                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
57457                     }
57458                     return defaults;
57459                 };
57460             }
57461
57462             docked.push(fbar);
57463             me.fbar = null;
57464         }
57465
57466         /**
57467          * @cfg {Object/Object[]} lbar
57468          * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
57469          *
57470          *     lbar: [
57471          *       { xtype: 'button', text: 'Button 1' }
57472          *     ]
57473          *
57474          * is equivalent to
57475          *
57476          *     dockedItems: [{
57477          *         xtype: 'toolbar',
57478          *         dock: 'left',
57479          *         items: [
57480          *             { xtype: 'button', text: 'Button 1' }
57481          *         ]
57482          *     }]
57483          */
57484         if (me.lbar) {
57485             docked.push(initToolbar(me.lbar, 'left'));
57486             me.lbar = null;
57487         }
57488
57489         /**
57490          * @cfg {Object/Object[]} rbar
57491          * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
57492          *
57493          *     rbar: [
57494          *       { xtype: 'button', text: 'Button 1' }
57495          *     ]
57496          *
57497          * is equivalent to
57498          *
57499          *     dockedItems: [{
57500          *         xtype: 'toolbar',
57501          *         dock: 'right',
57502          *         items: [
57503          *             { xtype: 'button', text: 'Button 1' }
57504          *         ]
57505          *     }]
57506          */
57507         if (me.rbar) {
57508             docked.push(initToolbar(me.rbar, 'right'));
57509             me.rbar = null;
57510         }
57511
57512         if (me.dockedItems) {
57513             if (!Ext.isArray(me.dockedItems)) {
57514                 me.dockedItems = [me.dockedItems];
57515             }
57516             me.dockedItems = me.dockedItems.concat(docked);
57517         } else {
57518             me.dockedItems = docked;
57519         }
57520     },
57521
57522     /**
57523      * @private
57524      * Tools are a Panel-specific capabilty.
57525      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
57526      */
57527     initTools: function() {
57528         var me = this;
57529
57530         me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
57531
57532         // Add a collapse tool unless configured to not show a collapse tool
57533         // or to not even show a header.
57534         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
57535             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
57536             me.collapseTool = me.expandTool = me.createComponent({
57537                 xtype: 'tool',
57538                 type: 'collapse-' + me.collapseDirection,
57539                 expandType: me.getOppositeDirection(me.collapseDirection),
57540                 handler: me.toggleCollapse,
57541                 scope: me
57542             });
57543
57544             // Prepend collapse tool is configured to do so.
57545             if (me.collapseFirst) {
57546                 me.tools.unshift(me.collapseTool);
57547             }
57548         }
57549
57550         // Add subclass-specific tools.
57551         me.addTools();
57552
57553         // Make Panel closable.
57554         if (me.closable) {
57555             me.addClsWithUI('closable');
57556             me.addTool({
57557                 type: 'close',
57558                 handler: Ext.Function.bind(me.close, this, [])
57559             });
57560         }
57561
57562         // Append collapse tool if needed.
57563         if (me.collapseTool && !me.collapseFirst) {
57564             me.tools.push(me.collapseTool);
57565         }
57566     },
57567
57568     /**
57569      * @private
57570      * @template
57571      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
57572      */
57573     addTools: Ext.emptyFn,
57574
57575     /**
57576      * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
57577      * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
57578      * close happens and will cancel the close action if it returns false.
57579      *
57580      * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
57581      * {@link #destroy} and {@link #hide} methods.
57582      */
57583     close: function() {
57584         if (this.fireEvent('beforeclose', this) !== false) {
57585             this.doClose();
57586         }
57587     },
57588
57589     // private
57590     doClose: function() {
57591         this.fireEvent('close', this);
57592         this[this.closeAction]();
57593     },
57594
57595     onRender: function(ct, position) {
57596         var me = this,
57597             topContainer;
57598
57599         // Add class-specific header tools.
57600         // Panel adds collapsible and closable.
57601         me.initTools();
57602
57603         // Dock the header/title
57604         me.updateHeader();
57605
57606         // Call to super after adding the header, to prevent an unnecessary re-layout
57607         me.callParent(arguments);
57608     },
57609
57610     afterRender: function() {
57611         var me = this;
57612
57613         me.callParent(arguments);
57614
57615         // Instate the collapsed state after render. We need to wait for
57616         // this moment so that we have established at least some of our size (from our
57617         // configured dimensions or from content via the component layout)
57618         if (me.collapsed) {
57619             me.collapsed = false;
57620             me.collapse(null, false, true);
57621         }
57622     },
57623
57624     /**
57625      * Create, hide, or show the header component as appropriate based on the current config.
57626      * @private
57627      * @param {Boolean} force True to force the header to be created
57628      */
57629     updateHeader: function(force) {
57630         var me = this,
57631             header = me.header,
57632             title = me.title,
57633             tools = me.tools;
57634
57635         if (!me.preventHeader && (force || title || (tools && tools.length))) {
57636             if (!header) {
57637                 header = me.header = Ext.create('Ext.panel.Header', {
57638                     title       : title,
57639                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
57640                     dock        : me.headerPosition || 'top',
57641                     textCls     : me.headerTextCls,
57642                     iconCls     : me.iconCls,
57643                     baseCls     : me.baseCls + '-header',
57644                     tools       : tools,
57645                     ui          : me.ui,
57646                     indicateDrag: me.draggable,
57647                     border      : me.border,
57648                     frame       : me.frame && me.frameHeader,
57649                     ignoreParentFrame : me.frame || me.overlapHeader,
57650                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
57651                     listeners   : me.collapsible && me.titleCollapse ? {
57652                         click: me.toggleCollapse,
57653                         scope: me
57654                     } : null
57655                 });
57656                 me.addDocked(header, 0);
57657
57658                 // Reference the Header's tool array.
57659                 // Header injects named references.
57660                 me.tools = header.tools;
57661             }
57662             header.show();
57663             me.initHeaderAria();
57664         } else if (header) {
57665             header.hide();
57666         }
57667     },
57668
57669     // inherit docs
57670     setUI: function(ui) {
57671         var me = this;
57672
57673         me.callParent(arguments);
57674
57675         if (me.header) {
57676             me.header.setUI(ui);
57677         }
57678     },
57679
57680     // private
57681     getContentTarget: function() {
57682         return this.body;
57683     },
57684
57685     getTargetEl: function() {
57686         return this.body || this.frameBody || this.el;
57687     },
57688
57689     // the overrides below allow for collapsed regions inside the border layout to be hidden
57690
57691     // inherit docs
57692     isVisible: function(deep){
57693         var me = this;
57694         if (me.collapsed && me.placeholder) {
57695             return me.placeholder.isVisible(deep);
57696         }
57697         return me.callParent(arguments);
57698     },
57699
57700     // inherit docs
57701     onHide: function(){
57702         var me = this;
57703         if (me.collapsed && me.placeholder) {
57704             me.placeholder.hide();
57705         } else {
57706             me.callParent(arguments);
57707         }
57708     },
57709
57710     // inherit docs
57711     onShow: function(){
57712         var me = this;
57713         if (me.collapsed && me.placeholder) {
57714             // force hidden back to true, since this gets set by the layout
57715             me.hidden = true;
57716             me.placeholder.show();
57717         } else {
57718             me.callParent(arguments);
57719         }
57720     },
57721
57722     addTool: function(tool) {
57723         var me = this,
57724             header = me.header;
57725
57726         if (Ext.isArray(tool)) {
57727             Ext.each(tool, me.addTool, me);
57728             return;
57729         }
57730         me.tools.push(tool);
57731         if (header) {
57732             header.addTool(tool);
57733         }
57734         me.updateHeader();
57735     },
57736
57737     getOppositeDirection: function(d) {
57738         var c = Ext.Component;
57739         switch (d) {
57740             case c.DIRECTION_TOP:
57741                 return c.DIRECTION_BOTTOM;
57742             case c.DIRECTION_RIGHT:
57743                 return c.DIRECTION_LEFT;
57744             case c.DIRECTION_BOTTOM:
57745                 return c.DIRECTION_TOP;
57746             case c.DIRECTION_LEFT:
57747                 return c.DIRECTION_RIGHT;
57748         }
57749     },
57750
57751     /**
57752      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
57753      * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
57754      * collapse action if it returns false.
57755      *
57756      * @param {String} direction . The direction to collapse towards. Must be one of
57757      *
57758      *   - Ext.Component.DIRECTION_TOP
57759      *   - Ext.Component.DIRECTION_RIGHT
57760      *   - Ext.Component.DIRECTION_BOTTOM
57761      *   - Ext.Component.DIRECTION_LEFT
57762      *
57763      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57764      * {@link #animCollapse} panel config)
57765      * @return {Ext.panel.Panel} this
57766      */
57767     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
57768         var me = this,
57769             c = Ext.Component,
57770             height = me.getHeight(),
57771             width = me.getWidth(),
57772             frameInfo,
57773             newSize = 0,
57774             dockedItems = me.dockedItems.items,
57775             dockedItemCount = dockedItems.length,
57776             i = 0,
57777             comp,
57778             pos,
57779             anim = {
57780                 from: {
57781                     height: height,
57782                     width: width
57783                 },
57784                 to: {
57785                     height: height,
57786                     width: width
57787                 },
57788                 listeners: {
57789                     afteranimate: me.afterCollapse,
57790                     scope: me
57791                 },
57792                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
57793             },
57794             reExpander,
57795             reExpanderOrientation,
57796             reExpanderDock,
57797             getDimension,
57798             collapseDimension;
57799
57800         if (!direction) {
57801             direction = me.collapseDirection;
57802         }
57803
57804         // If internal (Called because of initial collapsed state), then no animation, and no events.
57805         if (internal) {
57806             animate = false;
57807         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
57808             return false;
57809         }
57810
57811         reExpanderDock = direction;
57812         me.expandDirection = me.getOppositeDirection(direction);
57813
57814         // Track docked items which we hide during collapsed state
57815         me.hiddenDocked = [];
57816
57817         switch (direction) {
57818             case c.DIRECTION_TOP:
57819             case c.DIRECTION_BOTTOM:
57820                 reExpanderOrientation = 'horizontal';
57821                 collapseDimension = 'height';
57822                 getDimension = 'getHeight';
57823
57824                 // Attempt to find a reExpander Component (docked in a horizontal orientation)
57825                 // Also, collect all other docked items which we must hide after collapse.
57826                 for (; i < dockedItemCount; i++) {
57827                     comp = dockedItems[i];
57828                     if (comp.isVisible()) {
57829                         if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
57830                             reExpander = comp;
57831                         } else {
57832                             me.hiddenDocked.push(comp);
57833                         }
57834                     } else if (comp === me.reExpander) {
57835                         reExpander = comp;
57836                     }
57837                 }
57838
57839                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
57840                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57841                     anim.from.top = pos;
57842                 }
57843                 break;
57844
57845             case c.DIRECTION_LEFT:
57846             case c.DIRECTION_RIGHT:
57847                 reExpanderOrientation = 'vertical';
57848                 collapseDimension = 'width';
57849                 getDimension = 'getWidth';
57850
57851                 // Attempt to find a reExpander Component (docked in a vecrtical orientation)
57852                 // Also, collect all other docked items which we must hide after collapse.
57853                 for (; i < dockedItemCount; i++) {
57854                     comp = dockedItems[i];
57855                     if (comp.isVisible()) {
57856                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
57857                             reExpander = comp;
57858                         } else {
57859                             me.hiddenDocked.push(comp);
57860                         }
57861                     } else if (comp === me.reExpander) {
57862                         reExpander = comp;
57863                     }
57864                 }
57865
57866                 if (direction == Ext.Component.DIRECTION_RIGHT) {
57867                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57868                     anim.from.left = pos;
57869                 }
57870                 break;
57871
57872             default:
57873                 throw('Panel collapse must be passed a valid Component collapse direction');
57874         }
57875
57876         // Disable toggle tool during animated collapse
57877         if (animate && me.collapseTool) {
57878             me.collapseTool.disable();
57879         }
57880
57881         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
57882         me.addClsWithUI(me.collapsedCls);
57883         // if (me.border === false) {
57884         //     me.addClsWithUI(me.collapsedCls + '-noborder');
57885         // }
57886
57887         // We found a header: Measure it to find the collapse-to size.
57888         if (reExpander && reExpander.rendered) {
57889
57890             //we must add the collapsed cls to the header and then remove to get the proper height
57891             reExpander.addClsWithUI(me.collapsedCls);
57892             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57893             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57894                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57895             }
57896
57897             frameInfo = reExpander.getFrameInfo();
57898
57899             //get the size
57900             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
57901
57902             //and remove
57903             reExpander.removeClsWithUI(me.collapsedCls);
57904             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57905             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57906                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57907             }
57908         }
57909         // No header: Render and insert a temporary one, and then measure it.
57910         else {
57911             reExpander = {
57912                 hideMode: 'offsets',
57913                 temporary: true,
57914                 title: me.title,
57915                 orientation: reExpanderOrientation,
57916                 dock: reExpanderDock,
57917                 textCls: me.headerTextCls,
57918                 iconCls: me.iconCls,
57919                 baseCls: me.baseCls + '-header',
57920                 ui: me.ui,
57921                 frame: me.frame && me.frameHeader,
57922                 ignoreParentFrame: me.frame || me.overlapHeader,
57923                 indicateDrag: me.draggable,
57924                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
57925                 renderTo: me.el
57926             };
57927             if (!me.hideCollapseTool) {
57928                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
57929                     xtype: 'tool',
57930                     type: 'expand-' + me.expandDirection,
57931                     handler: me.toggleCollapse,
57932                     scope: me
57933                 }];
57934             }
57935
57936             // Capture the size of the re-expander.
57937             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
57938             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
57939             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
57940             reExpander.hide();
57941
57942             // Insert the new docked item
57943             me.insertDocked(0, reExpander);
57944         }
57945
57946         me.reExpander = reExpander;
57947         me.reExpander.addClsWithUI(me.collapsedCls);
57948         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57949         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57950             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57951         }
57952
57953         // If collapsing right or down, we'll be also animating the left or top.
57954         if (direction == Ext.Component.DIRECTION_RIGHT) {
57955             anim.to.left = pos + (width - newSize);
57956         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
57957             anim.to.top = pos + (height - newSize);
57958         }
57959
57960         // Animate to the new size
57961         anim.to[collapseDimension] = newSize;
57962
57963         // When we collapse a panel, the panel is in control of one dimension (depending on
57964         // collapse direction) and sets that on the component. We must restore the user's
57965         // original value (including non-existance) when we expand. Using this technique, we
57966         // mimic setCalculatedSize for the dimension we do not control and setSize for the
57967         // one we do (only while collapsed).
57968         if (!me.collapseMemento) {
57969             me.collapseMemento = new Ext.util.Memento(me);
57970         }
57971         me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
57972
57973         // Remove any flex config before we attempt to collapse.
57974         me.savedFlex = me.flex;
57975         me.minWidth = 0;
57976         me.minHeight = 0;
57977         delete me.flex;
57978         me.suspendLayout = true;
57979
57980         if (animate) {
57981             me.animate(anim);
57982         } else {
57983             me.setSize(anim.to.width, anim.to.height);
57984             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
57985                 me.setPosition(anim.to.left, anim.to.top);
57986             }
57987             me.afterCollapse(false, internal);
57988         }
57989         return me;
57990     },
57991
57992     afterCollapse: function(animated, internal) {
57993         var me = this,
57994             i = 0,
57995             l = me.hiddenDocked.length;
57996
57997         me.collapseMemento.restore(['minWidth', 'minHeight']);
57998
57999         // Now we can restore the dimension we don't control to its original state
58000         // Leave the value in the memento so that it can be correctly restored
58001         // if it is set by animation.
58002         if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
58003             me.layoutManagedHeight = 2;
58004             me.collapseMemento.restore('width', false);
58005         } else {
58006             me.layoutManagedWidth = 2;
58007             me.collapseMemento.restore('height', false);
58008         }
58009
58010         // We must hide the body, otherwise it overlays docked items which come before
58011         // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
58012         me.saveScrollTop = me.body.dom.scrollTop;
58013         me.body.setStyle('display', 'none');
58014
58015         for (; i < l; i++) {
58016             me.hiddenDocked[i].hide();
58017         }
58018         if (me.reExpander) {
58019             me.reExpander.updateFrame();
58020             me.reExpander.show();
58021         }
58022         me.collapsed = true;
58023         me.suspendLayout = false;
58024
58025         if (!internal) {
58026             if (me.ownerCt) {
58027                 // Because Component layouts only inform upstream containers if they have changed size,
58028                 // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
58029                 if (animated) {
58030                     me.ownerCt.layout.layout();
58031                 }
58032             } else if (me.reExpander.temporary) {
58033                 me.doComponentLayout();
58034             }
58035         }
58036
58037         if (me.resizer) {
58038             me.resizer.disable();
58039         }
58040
58041         // If me Panel was configured with a collapse tool in its header, flip it's type
58042         if (me.collapseTool) {
58043             me.collapseTool.setType('expand-' + me.expandDirection);
58044         }
58045         if (!internal) {
58046             me.fireEvent('collapse', me);
58047         }
58048
58049         // Re-enable the toggle tool after an animated collapse
58050         if (animated && me.collapseTool) {
58051             me.collapseTool.enable();
58052         }
58053     },
58054
58055     /**
58056      * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
58057      * expand action if it returns false.
58058      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
58059      * {@link #animCollapse} panel config)
58060      * @return {Ext.panel.Panel} this
58061      */
58062     expand: function(animate) {
58063         var me = this;
58064         if (!me.collapsed || me.fireEvent('beforeexpand', me, animate) === false) {
58065             return false;
58066         }
58067
58068         var i = 0,
58069             l = me.hiddenDocked.length,
58070             direction = me.expandDirection,
58071             height = me.getHeight(),
58072             width = me.getWidth(),
58073             pos, anim;
58074
58075         // Disable toggle tool during animated expand
58076         if (animate && me.collapseTool) {
58077             me.collapseTool.disable();
58078         }
58079
58080         // Show any docked items that we hid on collapse
58081         // And hide the injected reExpander Header
58082         for (; i < l; i++) {
58083             me.hiddenDocked[i].hidden = false;
58084             me.hiddenDocked[i].el.show();
58085         }
58086         if (me.reExpander) {
58087             if (me.reExpander.temporary) {
58088                 me.reExpander.hide();
58089             } else {
58090                 me.reExpander.removeClsWithUI(me.collapsedCls);
58091                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
58092                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
58093                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
58094                 }
58095                 me.reExpander.updateFrame();
58096             }
58097         }
58098
58099         // If me Panel was configured with a collapse tool in its header, flip it's type
58100         if (me.collapseTool) {
58101             me.collapseTool.setType('collapse-' + me.collapseDirection);
58102         }
58103
58104         // Restore body display and scroll position
58105         me.body.setStyle('display', '');
58106         me.body.dom.scrollTop = me.saveScrollTop;
58107
58108         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
58109         me.collapsed = false;
58110
58111         // Remove any collapsed styling before any animation begins
58112         me.removeClsWithUI(me.collapsedCls);
58113         // if (me.border === false) {
58114         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
58115         // }
58116
58117         anim = {
58118             to: {
58119             },
58120             from: {
58121                 height: height,
58122                 width: width
58123             },
58124             listeners: {
58125                 afteranimate: me.afterExpand,
58126                 scope: me
58127             }
58128         };
58129
58130         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
58131
58132             // Restore the collapsed dimension.
58133             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
58134             me.collapseMemento.restore('height', false);
58135
58136             // If autoHeight, measure the height now we have shown the body element.
58137             if (me.height === undefined) {
58138                 me.setCalculatedSize(me.width, null);
58139                 anim.to.height = me.getHeight();
58140
58141                 // Must size back down to collapsed for the animation.
58142                 me.setCalculatedSize(me.width, anim.from.height);
58143             }
58144             // If we were flexed, then we can't just restore to the saved size.
58145             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
58146             else if (me.savedFlex) {
58147                 me.flex = me.savedFlex;
58148                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
58149                 delete me.flex;
58150             }
58151             // Else, restore to saved height
58152             else {
58153                 anim.to.height = me.height;
58154             }
58155
58156             // top needs animating upwards
58157             if (direction == Ext.Component.DIRECTION_TOP) {
58158                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
58159                 anim.from.top = pos;
58160                 anim.to.top = pos - (anim.to.height - height);
58161             }
58162         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
58163
58164             // Restore the collapsed dimension.
58165             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
58166             me.collapseMemento.restore('width', false);
58167
58168             // If autoWidth, measure the width now we have shown the body element.
58169             if (me.width === undefined) {
58170                 me.setCalculatedSize(null, me.height);
58171                 anim.to.width = me.getWidth();
58172
58173                 // Must size back down to collapsed for the animation.
58174                 me.setCalculatedSize(anim.from.width, me.height);
58175             }
58176             // If we were flexed, then we can't just restore to the saved size.
58177             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
58178             else if (me.savedFlex) {
58179                 me.flex = me.savedFlex;
58180                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
58181                 delete me.flex;
58182             }
58183             // Else, restore to saved width
58184             else {
58185                 anim.to.width = me.width;
58186             }
58187
58188             // left needs animating leftwards
58189             if (direction == Ext.Component.DIRECTION_LEFT) {
58190                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
58191                 anim.from.left = pos;
58192                 anim.to.left = pos - (anim.to.width - width);
58193             }
58194         }
58195
58196         if (animate) {
58197             me.animate(anim);
58198         } else {
58199             me.setCalculatedSize(anim.to.width, anim.to.height);
58200             if (anim.to.x) {
58201                 me.setLeft(anim.to.x);
58202             }
58203             if (anim.to.y) {
58204                 me.setTop(anim.to.y);
58205             }
58206             me.afterExpand(false);
58207         }
58208
58209         return me;
58210     },
58211
58212     afterExpand: function(animated) {
58213         var me = this;
58214
58215         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
58216         if (me.savedFlex) {
58217             me.flex = me.savedFlex;
58218             delete me.savedFlex;
58219             delete me.width;
58220             delete me.height;
58221         }
58222
58223         // Restore width/height and dimension management flags to original values
58224         if (me.collapseMemento) {
58225             me.collapseMemento.restoreAll();
58226         }
58227
58228         if (animated && me.ownerCt) {
58229             // IE 6 has an intermittent repaint issue in this case so give
58230             // it a little extra time to catch up before laying out.
58231             Ext.defer(me.ownerCt.doLayout, Ext.isIE6 ? 1 : 0, me);
58232         }
58233
58234         if (me.resizer) {
58235             me.resizer.enable();
58236         }
58237
58238         me.fireEvent('expand', me);
58239
58240         // Re-enable the toggle tool after an animated expand
58241         if (animated && me.collapseTool) {
58242             me.collapseTool.enable();
58243         }
58244     },
58245
58246     /**
58247      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
58248      * @return {Ext.panel.Panel} this
58249      */
58250     toggleCollapse: function() {
58251         if (this.collapsed) {
58252             this.expand(this.animCollapse);
58253         } else {
58254             this.collapse(this.collapseDirection, this.animCollapse);
58255         }
58256         return this;
58257     },
58258
58259     // private
58260     getKeyMap : function(){
58261         if(!this.keyMap){
58262             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
58263         }
58264         return this.keyMap;
58265     },
58266
58267     // private
58268     initDraggable : function(){
58269         /**
58270          * @property {Ext.dd.DragSource} dd
58271          * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
58272          * Ext.dd.DragSource} which handles dragging the Panel.
58273          *
58274          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
58275          * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
58276          */
58277         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
58278     },
58279
58280     // private - helper function for ghost
58281     ghostTools : function() {
58282         var tools = [],
58283             headerTools = this.header.query('tool[hidden=false]');
58284
58285         if (headerTools.length) {
58286             Ext.each(headerTools, function(tool) {
58287                 // Some tools can be full components, and copying them into the ghost
58288                 // actually removes them from the owning panel. You could also potentially
58289                 // end up with duplicate DOM ids as well. To avoid any issues we just make
58290                 // a simple bare-minimum clone of each tool for ghosting purposes.
58291                 tools.push({
58292                     type: tool.type
58293                 });
58294             });
58295         } else {
58296             tools = [{
58297                 type: 'placeholder'
58298             }];
58299         }
58300         return tools;
58301     },
58302
58303     // private - used for dragging
58304     ghost: function(cls) {
58305         var me = this,
58306             ghostPanel = me.ghostPanel,
58307             box = me.getBox(),
58308             header;
58309
58310         if (!ghostPanel) {
58311             ghostPanel = Ext.create('Ext.panel.Panel', {
58312                 renderTo: me.floating ? me.el.dom.parentNode : document.body,
58313                 floating: {
58314                     shadow: false
58315                 },
58316                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
58317                 overlapHeader: me.overlapHeader,
58318                 headerPosition: me.headerPosition,
58319                 baseCls: me.baseCls,
58320                 cls: me.baseCls + '-ghost ' + (cls ||'')
58321             });
58322             me.ghostPanel = ghostPanel;
58323         }
58324         ghostPanel.floatParent = me.floatParent;
58325         if (me.floating) {
58326             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
58327         } else {
58328             ghostPanel.toFront();
58329         }
58330         header = ghostPanel.header;
58331         // restore options
58332         if (header) {
58333             header.suspendLayout = true;
58334             Ext.Array.forEach(header.query('tool'), function(tool){
58335                 header.remove(tool);
58336             });
58337             header.suspendLayout = false;
58338         }
58339         ghostPanel.addTool(me.ghostTools());
58340         ghostPanel.setTitle(me.title);
58341         ghostPanel.setIconCls(me.iconCls);
58342
58343         ghostPanel.el.show();
58344         ghostPanel.setPosition(box.x, box.y);
58345         ghostPanel.setSize(box.width, box.height);
58346         me.el.hide();
58347         if (me.floatingItems) {
58348             me.floatingItems.hide();
58349         }
58350         return ghostPanel;
58351     },
58352
58353     // private
58354     unghost: function(show, matchPosition) {
58355         var me = this;
58356         if (!me.ghostPanel) {
58357             return;
58358         }
58359         if (show !== false) {
58360             me.el.show();
58361             if (matchPosition !== false) {
58362                 me.setPosition(me.ghostPanel.getPosition());
58363             }
58364             if (me.floatingItems) {
58365                 me.floatingItems.show();
58366             }
58367             Ext.defer(me.focus, 10, me);
58368         }
58369         me.ghostPanel.el.hide();
58370     },
58371
58372     initResizable: function(resizable) {
58373         if (this.collapsed) {
58374             resizable.disabled = true;
58375         }
58376         this.callParent([resizable]);
58377     }
58378 }, function(){
58379     this.prototype.animCollapse = Ext.enableFx;
58380 });
58381
58382 /**
58383  * Component layout for Tip/ToolTip/etc. components
58384  * @class Ext.layout.component.Tip
58385  * @extends Ext.layout.component.Dock
58386  * @private
58387  */
58388
58389 Ext.define('Ext.layout.component.Tip', {
58390
58391     /* Begin Definitions */
58392
58393     alias: ['layout.tip'],
58394
58395     extend: 'Ext.layout.component.Dock',
58396
58397     /* End Definitions */
58398
58399     type: 'tip',
58400     
58401     onLayout: function(width, height) {
58402         var me = this,
58403             owner = me.owner,
58404             el = owner.el,
58405             minWidth,
58406             maxWidth,
58407             naturalWidth,
58408             constrainedWidth,
58409             xy = el.getXY();
58410
58411         // Position offscreen so the natural width is not affected by the viewport's right edge
58412         el.setXY([-9999,-9999]);
58413
58414         // Calculate initial layout
58415         this.callParent(arguments);
58416
58417         // Handle min/maxWidth for auto-width tips
58418         if (!Ext.isNumber(width)) {
58419             minWidth = owner.minWidth;
58420             maxWidth = owner.maxWidth;
58421             // IE6/7 in strict mode have a problem doing an autoWidth
58422             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
58423                 constrainedWidth = me.doAutoWidth();
58424             } else {
58425                 naturalWidth = el.getWidth();
58426             }
58427             if (naturalWidth < minWidth) {
58428                 constrainedWidth = minWidth;
58429             }
58430             else if (naturalWidth > maxWidth) {
58431                 constrainedWidth = maxWidth;
58432             }
58433             if (constrainedWidth) {
58434                 this.callParent([constrainedWidth, height]);
58435             }
58436         }
58437
58438         // Restore position
58439         el.setXY(xy);
58440     },
58441     
58442     doAutoWidth: function(){
58443         var me = this,
58444             owner = me.owner,
58445             body = owner.body,
58446             width = body.getTextWidth();
58447             
58448         if (owner.header) {
58449             width = Math.max(width, owner.header.getWidth());
58450         }
58451         if (!Ext.isDefined(me.frameWidth)) {
58452             me.frameWidth = owner.el.getWidth() - body.getWidth();
58453         }
58454         width += me.frameWidth + body.getPadding('lr');
58455         return width;
58456     }
58457 });
58458
58459 /**
58460  * @class Ext.tip.Tip
58461  * @extends Ext.panel.Panel
58462  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
58463  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
58464  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
58465  * @xtype tip
58466  */
58467 Ext.define('Ext.tip.Tip', {
58468     extend: 'Ext.panel.Panel',
58469     requires: [ 'Ext.layout.component.Tip' ],
58470     alternateClassName: 'Ext.Tip',
58471     /**
58472      * @cfg {Boolean} [closable=false]
58473      * True to render a close tool button into the tooltip header.
58474      */
58475     /**
58476      * @cfg {Number} width
58477      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
58478      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
58479      */
58480     /**
58481      * @cfg {Number} minWidth The minimum width of the tip in pixels.
58482      */
58483     minWidth : 40,
58484     /**
58485      * @cfg {Number} maxWidth The maximum width of the tip in pixels.  The maximum supported value is 500.
58486      */
58487     maxWidth : 300,
58488     /**
58489      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
58490      * for bottom-right shadow.
58491      */
58492     shadow : "sides",
58493
58494     /**
58495      * @cfg {String} defaultAlign
58496      * <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value for this tip relative
58497      * to its element of origin.
58498      */
58499     defaultAlign : "tl-bl?",
58500     /**
58501      * @cfg {Boolean} constrainPosition
58502      * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
58503      */
58504     constrainPosition : true,
58505
58506     // @inherited
58507     frame: false,
58508
58509     // private panel overrides
58510     autoRender: true,
58511     hidden: true,
58512     baseCls: Ext.baseCSSPrefix + 'tip',
58513     floating: {
58514         shadow: true,
58515         shim: true,
58516         constrain: true
58517     },
58518     focusOnToFront: false,
58519     componentLayout: 'tip',
58520
58521     /**
58522      * @cfg {String} closeAction
58523      * <p>The action to take when the close header tool is clicked:
58524      * <div class="mdetail-params"><ul>
58525      * <li><b><code>'{@link #destroy}'</code></b> : <div class="sub-desc">
58526      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
58527      * it and all descendant Components. The window will <b>not</b> be available to be
58528      * redisplayed via the {@link #show} method.
58529      * </div></li>
58530      * <li><b><code>'{@link #hide}'</code></b> : <b>Default</b><div class="sub-desc">
58531      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
58532      * The window will be available to be redisplayed via the {@link #show} method.
58533      * </div></li>
58534      * </ul></div>
58535      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
58536      * which will invoke the approriate closeAction.
58537      */
58538     closeAction: 'hide',
58539
58540     ariaRole: 'tooltip',
58541
58542     initComponent: function() {
58543         var me = this;
58544
58545         me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
58546         me.callParent(arguments);
58547
58548         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
58549         me.constrain = me.constrain || me.constrainPosition;
58550     },
58551
58552     /**
58553      * Shows this tip at the specified XY position.  Example usage:
58554      * <pre><code>
58555 // Show the tip at x:50 and y:100
58556 tip.showAt([50,100]);
58557 </code></pre>
58558      * @param {Number[]} xy An array containing the x and y coordinates
58559      */
58560     showAt : function(xy){
58561         var me = this;
58562         this.callParent(arguments);
58563         // Show may have been vetoed.
58564         if (me.isVisible()) {
58565             me.setPagePosition(xy[0], xy[1]);
58566             if (me.constrainPosition || me.constrain) {
58567                 me.doConstrain();
58568             }
58569             me.toFront(true);
58570         }
58571     },
58572
58573     /**
58574      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
58575      * anchor position value.  Example usage:
58576      * <pre><code>
58577 // Show the tip at the default position ('tl-br?')
58578 tip.showBy('my-el');
58579
58580 // Show the tip's top-left corner anchored to the element's top-right corner
58581 tip.showBy('my-el', 'tl-tr');
58582 </code></pre>
58583      * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
58584      * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
58585      * {@link #defaultAlign} if specified).
58586      */
58587     showBy : function(el, pos) {
58588         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
58589     },
58590
58591     /**
58592      * @private
58593      * @override
58594      * Set Tip draggable using base Component's draggability
58595      */
58596     initDraggable : function(){
58597         var me = this;
58598         me.draggable = {
58599             el: me.getDragEl(),
58600             delegate: me.header.el,
58601             constrain: me,
58602             constrainTo: me.el.getScopeParent()
58603         };
58604         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
58605         Ext.Component.prototype.initDraggable.call(me);
58606     },
58607
58608     // Tip does not ghost. Drag is "live"
58609     ghost: undefined,
58610     unghost: undefined
58611 });
58612
58613 /**
58614  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
58615  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
58616  * control over the tooltip's alignment relative to the target element or mouse, and the timing
58617  * of when it is automatically shown and hidden.
58618  *
58619  * This implementation does **not** have a built-in method of automatically populating the tooltip's
58620  * text based on the target element; you must either configure a fixed {@link #html} value for each
58621  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
58622  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
58623  * convenient way of automatically populating and configuring a tooltip based on specific DOM
58624  * attributes of each target element.
58625  *
58626  * # Basic Example
58627  *
58628  *     var tip = Ext.create('Ext.tip.ToolTip', {
58629  *         target: 'clearButton',
58630  *         html: 'Press this button to clear the form'
58631  *     });
58632  *
58633  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
58634  *
58635  * # Delegation
58636  *
58637  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
58638  * one ToolTip to many elements under a common parent. This is more efficient than creating many
58639  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
58640  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
58641  * appropriate sub-elements.
58642  *
58643  * When using delegation, it is likely that you will want to programmatically change the content
58644  * of the ToolTip based on each delegate element; you can do this by implementing a custom
58645  * listener for the {@link #beforeshow} event. Example:
58646  *
58647  *     var store = Ext.create('Ext.data.ArrayStore', {
58648  *         fields: ['company', 'price', 'change'],
58649  *         data: [
58650  *             ['3m Co',                               71.72, 0.02],
58651  *             ['Alcoa Inc',                           29.01, 0.42],
58652  *             ['Altria Group Inc',                    83.81, 0.28],
58653  *             ['American Express Company',            52.55, 0.01],
58654  *             ['American International Group, Inc.',  64.13, 0.31],
58655  *             ['AT&T Inc.',                           31.61, -0.48]
58656  *         ]
58657  *     });
58658  *
58659  *     var grid = Ext.create('Ext.grid.Panel', {
58660  *         title: 'Array Grid',
58661  *         store: store,
58662  *         columns: [
58663  *             {text: 'Company', flex: 1, dataIndex: 'company'},
58664  *             {text: 'Price', width: 75, dataIndex: 'price'},
58665  *             {text: 'Change', width: 75, dataIndex: 'change'}
58666  *         ],
58667  *         height: 200,
58668  *         width: 400,
58669  *         renderTo: Ext.getBody()
58670  *     });
58671  *
58672  *     grid.getView().on('render', function(view) {
58673  *         view.tip = Ext.create('Ext.tip.ToolTip', {
58674  *             // The overall target element.
58675  *             target: view.el,
58676  *             // Each grid row causes its own seperate show and hide.
58677  *             delegate: view.itemSelector,
58678  *             // Moving within the row should not hide the tip.
58679  *             trackMouse: true,
58680  *             // Render immediately so that tip.body can be referenced prior to the first show.
58681  *             renderTo: Ext.getBody(),
58682  *             listeners: {
58683  *                 // Change content dynamically depending on which element triggered the show.
58684  *                 beforeshow: function updateTipBody(tip) {
58685  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
58686  *                 }
58687  *             }
58688  *         });
58689  *     });
58690  *
58691  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
58692  *
58693  * # Alignment
58694  *
58695  * The following configuration properties allow control over how the ToolTip is aligned relative to
58696  * the target element and/or mouse pointer:
58697  *
58698  * - {@link #anchor}
58699  * - {@link #anchorToTarget}
58700  * - {@link #anchorOffset}
58701  * - {@link #trackMouse}
58702  * - {@link #mouseOffset}
58703  *
58704  * # Showing/Hiding
58705  *
58706  * The following configuration properties allow control over how and when the ToolTip is automatically
58707  * shown and hidden:
58708  *
58709  * - {@link #autoHide}
58710  * - {@link #showDelay}
58711  * - {@link #hideDelay}
58712  * - {@link #dismissDelay}
58713  *
58714  * @docauthor Jason Johnston <jason@sencha.com>
58715  */
58716 Ext.define('Ext.tip.ToolTip', {
58717     extend: 'Ext.tip.Tip',
58718     alias: 'widget.tooltip',
58719     alternateClassName: 'Ext.ToolTip',
58720     /**
58721      * @property {HTMLElement} triggerElement
58722      * When a ToolTip is configured with the `{@link #delegate}`
58723      * option to cause selected child elements of the `{@link #target}`
58724      * Element to each trigger a seperate show event, this property is set to
58725      * the DOM element which triggered the show.
58726      */
58727     /**
58728      * @cfg {HTMLElement/Ext.Element/String} target
58729      * The target element or string id to monitor for mouseover events to trigger
58730      * showing this ToolTip.
58731      */
58732     /**
58733      * @cfg {Boolean} [autoHide=true]
58734      * True to automatically hide the tooltip after the
58735      * mouse exits the target element or after the `{@link #dismissDelay}`
58736      * has expired if set.  If `{@link #closable} = true`
58737      * a close tool button will be rendered into the tooltip header.
58738      */
58739     /**
58740      * @cfg {Number} showDelay
58741      * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
58742      */
58743     showDelay: 500,
58744     /**
58745      * @cfg {Number} hideDelay
58746      * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
58747      * Set to 0 for the tooltip to hide immediately.
58748      */
58749     hideDelay: 200,
58750     /**
58751      * @cfg {Number} dismissDelay
58752      * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
58753      * dismissDelay = 0.
58754      */
58755     dismissDelay: 5000,
58756     /**
58757      * @cfg {Number[]} [mouseOffset=[15,18]]
58758      * An XY offset from the mouse position where the tooltip should be shown.
58759      */
58760     /**
58761      * @cfg {Boolean} trackMouse
58762      * True to have the tooltip follow the mouse as it moves over the target element.
58763      */
58764     trackMouse: false,
58765     /**
58766      * @cfg {String} anchor
58767      * If specified, indicates that the tip should be anchored to a
58768      * particular side of the target element or mouse pointer ("top", "right", "bottom",
58769      * or "left"), with an arrow pointing back at the target or mouse pointer. If
58770      * {@link #constrainPosition} is enabled, this will be used as a preferred value
58771      * only and may be flipped as needed.
58772      */
58773     /**
58774      * @cfg {Boolean} anchorToTarget
58775      * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
58776      * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
58777      * target element.  When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
58778      */
58779     anchorToTarget: true,
58780     /**
58781      * @cfg {Number} anchorOffset
58782      * A numeric pixel value used to offset the default position of the anchor arrow.  When the anchor
58783      * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
58784      * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
58785      * a vertical offset.
58786      */
58787     anchorOffset: 0,
58788     /**
58789      * @cfg {String} delegate
58790      *
58791      * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
58792      * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
58793      * target.
58794      *
58795      * When specified, the child element of the target which caused a show event is placed into the
58796      * `{@link #triggerElement}` property before the ToolTip is shown.
58797      *
58798      * This may be useful when a Component has regular, repeating elements in it, each of which need a
58799      * ToolTip which contains information specific to that element.
58800      *
58801      * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
58802      */
58803
58804     // private
58805     targetCounter: 0,
58806     quickShowInterval: 250,
58807
58808     // private
58809     initComponent: function() {
58810         var me = this;
58811         me.callParent(arguments);
58812         me.lastActive = new Date();
58813         me.setTarget(me.target);
58814         me.origAnchor = me.anchor;
58815     },
58816
58817     // private
58818     onRender: function(ct, position) {
58819         var me = this;
58820         me.callParent(arguments);
58821         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58822         me.anchorEl = me.el.createChild({
58823             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
58824         });
58825     },
58826
58827     // private
58828     afterRender: function() {
58829         var me = this,
58830             zIndex;
58831
58832         me.callParent(arguments);
58833         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
58834         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
58835     },
58836
58837     /**
58838      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
58839      * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
58840      */
58841     setTarget: function(target) {
58842         var me = this,
58843             t = Ext.get(target),
58844             tg;
58845
58846         if (me.target) {
58847             tg = Ext.get(me.target);
58848             me.mun(tg, 'mouseover', me.onTargetOver, me);
58849             me.mun(tg, 'mouseout', me.onTargetOut, me);
58850             me.mun(tg, 'mousemove', me.onMouseMove, me);
58851         }
58852
58853         me.target = t;
58854         if (t) {
58855
58856             me.mon(t, {
58857                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
58858                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
58859                 freezeEvent: true,
58860
58861                 mouseover: me.onTargetOver,
58862                 mouseout: me.onTargetOut,
58863                 mousemove: me.onMouseMove,
58864                 scope: me
58865             });
58866         }
58867         if (me.anchor) {
58868             me.anchorTarget = me.target;
58869         }
58870     },
58871
58872     // private
58873     onMouseMove: function(e) {
58874         var me = this,
58875             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
58876             xy;
58877         if (t) {
58878             me.targetXY = e.getXY();
58879             if (t === me.triggerElement) {
58880                 if (!me.hidden && me.trackMouse) {
58881                     xy = me.getTargetXY();
58882                     if (me.constrainPosition) {
58883                         xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
58884                     }
58885                     me.setPagePosition(xy);
58886                 }
58887             } else {
58888                 me.hide();
58889                 me.lastActive = new Date(0);
58890                 me.onTargetOver(e);
58891             }
58892         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
58893             me.hide();
58894         }
58895     },
58896
58897     // private
58898     getTargetXY: function() {
58899         var me = this,
58900             mouseOffset;
58901         if (me.delegate) {
58902             me.anchorTarget = me.triggerElement;
58903         }
58904         if (me.anchor) {
58905             me.targetCounter++;
58906                 var offsets = me.getOffsets(),
58907                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
58908                     dw = Ext.Element.getViewWidth() - 5,
58909                     dh = Ext.Element.getViewHeight() - 5,
58910                     de = document.documentElement,
58911                     bd = document.body,
58912                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
58913                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
58914                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
58915                     sz = me.getSize(),
58916                     constrainPosition = me.constrainPosition;
58917
58918             me.anchorEl.removeCls(me.anchorCls);
58919
58920             if (me.targetCounter < 2 && constrainPosition) {
58921                 if (axy[0] < scrollX) {
58922                     if (me.anchorToTarget) {
58923                         me.defaultAlign = 'l-r';
58924                         if (me.mouseOffset) {
58925                             me.mouseOffset[0] *= -1;
58926                         }
58927                     }
58928                     me.anchor = 'left';
58929                     return me.getTargetXY();
58930                 }
58931                 if (axy[0] + sz.width > dw) {
58932                     if (me.anchorToTarget) {
58933                         me.defaultAlign = 'r-l';
58934                         if (me.mouseOffset) {
58935                             me.mouseOffset[0] *= -1;
58936                         }
58937                     }
58938                     me.anchor = 'right';
58939                     return me.getTargetXY();
58940                 }
58941                 if (axy[1] < scrollY) {
58942                     if (me.anchorToTarget) {
58943                         me.defaultAlign = 't-b';
58944                         if (me.mouseOffset) {
58945                             me.mouseOffset[1] *= -1;
58946                         }
58947                     }
58948                     me.anchor = 'top';
58949                     return me.getTargetXY();
58950                 }
58951                 if (axy[1] + sz.height > dh) {
58952                     if (me.anchorToTarget) {
58953                         me.defaultAlign = 'b-t';
58954                         if (me.mouseOffset) {
58955                             me.mouseOffset[1] *= -1;
58956                         }
58957                     }
58958                     me.anchor = 'bottom';
58959                     return me.getTargetXY();
58960                 }
58961             }
58962
58963             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58964             me.anchorEl.addCls(me.anchorCls);
58965             me.targetCounter = 0;
58966             return axy;
58967         } else {
58968             mouseOffset = me.getMouseOffset();
58969             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
58970         }
58971     },
58972
58973     getMouseOffset: function() {
58974         var me = this,
58975         offset = me.anchor ? [0, 0] : [15, 18];
58976         if (me.mouseOffset) {
58977             offset[0] += me.mouseOffset[0];
58978             offset[1] += me.mouseOffset[1];
58979         }
58980         return offset;
58981     },
58982
58983     // private
58984     getAnchorPosition: function() {
58985         var me = this,
58986             m;
58987         if (me.anchor) {
58988             me.tipAnchor = me.anchor.charAt(0);
58989         } else {
58990             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
58991             if (!m) {
58992                 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
58993             }
58994             me.tipAnchor = m[1].charAt(0);
58995         }
58996
58997         switch (me.tipAnchor) {
58998         case 't':
58999             return 'top';
59000         case 'b':
59001             return 'bottom';
59002         case 'r':
59003             return 'right';
59004         }
59005         return 'left';
59006     },
59007
59008     // private
59009     getAnchorAlign: function() {
59010         switch (this.anchor) {
59011         case 'top':
59012             return 'tl-bl';
59013         case 'left':
59014             return 'tl-tr';
59015         case 'right':
59016             return 'tr-tl';
59017         default:
59018             return 'bl-tl';
59019         }
59020     },
59021
59022     // private
59023     getOffsets: function() {
59024         var me = this,
59025             mouseOffset,
59026             offsets,
59027             ap = me.getAnchorPosition().charAt(0);
59028         if (me.anchorToTarget && !me.trackMouse) {
59029             switch (ap) {
59030             case 't':
59031                 offsets = [0, 9];
59032                 break;
59033             case 'b':
59034                 offsets = [0, -13];
59035                 break;
59036             case 'r':
59037                 offsets = [ - 13, 0];
59038                 break;
59039             default:
59040                 offsets = [9, 0];
59041                 break;
59042             }
59043         } else {
59044             switch (ap) {
59045             case 't':
59046                 offsets = [ - 15 - me.anchorOffset, 30];
59047                 break;
59048             case 'b':
59049                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
59050                 break;
59051             case 'r':
59052                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
59053                 break;
59054             default:
59055                 offsets = [25, -13 - me.anchorOffset];
59056                 break;
59057             }
59058         }
59059         mouseOffset = me.getMouseOffset();
59060         offsets[0] += mouseOffset[0];
59061         offsets[1] += mouseOffset[1];
59062
59063         return offsets;
59064     },
59065
59066     // private
59067     onTargetOver: function(e) {
59068         var me = this,
59069             t;
59070
59071         if (me.disabled || e.within(me.target.dom, true)) {
59072             return;
59073         }
59074         t = e.getTarget(me.delegate);
59075         if (t) {
59076             me.triggerElement = t;
59077             me.clearTimer('hide');
59078             me.targetXY = e.getXY();
59079             me.delayShow();
59080         }
59081     },
59082
59083     // private
59084     delayShow: function() {
59085         var me = this;
59086         if (me.hidden && !me.showTimer) {
59087             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
59088                 me.show();
59089             } else {
59090                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
59091             }
59092         }
59093         else if (!me.hidden && me.autoHide !== false) {
59094             me.show();
59095         }
59096     },
59097
59098     // private
59099     onTargetOut: function(e) {
59100         var me = this;
59101         if (me.disabled || e.within(me.target.dom, true)) {
59102             return;
59103         }
59104         me.clearTimer('show');
59105         if (me.autoHide !== false) {
59106             me.delayHide();
59107         }
59108     },
59109
59110     // private
59111     delayHide: function() {
59112         var me = this;
59113         if (!me.hidden && !me.hideTimer) {
59114             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
59115         }
59116     },
59117
59118     /**
59119      * Hides this tooltip if visible.
59120      */
59121     hide: function() {
59122         var me = this;
59123         me.clearTimer('dismiss');
59124         me.lastActive = new Date();
59125         if (me.anchorEl) {
59126             me.anchorEl.hide();
59127         }
59128         me.callParent(arguments);
59129         delete me.triggerElement;
59130     },
59131
59132     /**
59133      * Shows this tooltip at the current event target XY position.
59134      */
59135     show: function() {
59136         var me = this;
59137
59138         // Show this Component first, so that sizing can be calculated
59139         // pre-show it off screen so that the el will have dimensions
59140         this.callParent();
59141         if (this.hidden === false) {
59142             me.setPagePosition(-10000, -10000);
59143
59144             if (me.anchor) {
59145                 me.anchor = me.origAnchor;
59146             }
59147             me.showAt(me.getTargetXY());
59148
59149             if (me.anchor) {
59150                 me.syncAnchor();
59151                 me.anchorEl.show();
59152             } else {
59153                 me.anchorEl.hide();
59154             }
59155         }
59156     },
59157
59158     // inherit docs
59159     showAt: function(xy) {
59160         var me = this;
59161         me.lastActive = new Date();
59162         me.clearTimers();
59163
59164         // Only call if this is hidden. May have been called from show above.
59165         if (!me.isVisible()) {
59166             this.callParent(arguments);
59167         }
59168
59169         // Show may have been vetoed.
59170         if (me.isVisible()) {
59171             me.setPagePosition(xy[0], xy[1]);
59172             if (me.constrainPosition || me.constrain) {
59173                 me.doConstrain();
59174             }
59175             me.toFront(true);
59176         }
59177
59178         if (me.dismissDelay && me.autoHide !== false) {
59179             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
59180         }
59181         if (me.anchor) {
59182             me.syncAnchor();
59183             if (!me.anchorEl.isVisible()) {
59184                 me.anchorEl.show();
59185             }
59186         } else {
59187             me.anchorEl.hide();
59188         }
59189     },
59190
59191     // private
59192     syncAnchor: function() {
59193         var me = this,
59194             anchorPos,
59195             targetPos,
59196             offset;
59197         switch (me.tipAnchor.charAt(0)) {
59198         case 't':
59199             anchorPos = 'b';
59200             targetPos = 'tl';
59201             offset = [20 + me.anchorOffset, 1];
59202             break;
59203         case 'r':
59204             anchorPos = 'l';
59205             targetPos = 'tr';
59206             offset = [ - 1, 12 + me.anchorOffset];
59207             break;
59208         case 'b':
59209             anchorPos = 't';
59210             targetPos = 'bl';
59211             offset = [20 + me.anchorOffset, -1];
59212             break;
59213         default:
59214             anchorPos = 'r';
59215             targetPos = 'tl';
59216             offset = [1, 12 + me.anchorOffset];
59217             break;
59218         }
59219         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
59220     },
59221
59222     // private
59223     setPagePosition: function(x, y) {
59224         var me = this;
59225         me.callParent(arguments);
59226         if (me.anchor) {
59227             me.syncAnchor();
59228         }
59229     },
59230
59231     // private
59232     clearTimer: function(name) {
59233         name = name + 'Timer';
59234         clearTimeout(this[name]);
59235         delete this[name];
59236     },
59237
59238     // private
59239     clearTimers: function() {
59240         var me = this;
59241         me.clearTimer('show');
59242         me.clearTimer('dismiss');
59243         me.clearTimer('hide');
59244     },
59245
59246     // private
59247     onShow: function() {
59248         var me = this;
59249         me.callParent();
59250         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
59251     },
59252
59253     // private
59254     onHide: function() {
59255         var me = this;
59256         me.callParent();
59257         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
59258     },
59259
59260     // private
59261     onDocMouseDown: function(e) {
59262         var me = this;
59263         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
59264             me.disable();
59265             Ext.defer(me.doEnable, 100, me);
59266         }
59267     },
59268
59269     // private
59270     doEnable: function() {
59271         if (!this.isDestroyed) {
59272             this.enable();
59273         }
59274     },
59275
59276     // private
59277     onDisable: function() {
59278         this.callParent();
59279         this.clearTimers();
59280         this.hide();
59281     },
59282
59283     beforeDestroy: function() {
59284         var me = this;
59285         me.clearTimers();
59286         Ext.destroy(me.anchorEl);
59287         delete me.anchorEl;
59288         delete me.target;
59289         delete me.anchorTarget;
59290         delete me.triggerElement;
59291         me.callParent();
59292     },
59293
59294     // private
59295     onDestroy: function() {
59296         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
59297         this.callParent();
59298     }
59299 });
59300
59301 /**
59302  * @class Ext.tip.QuickTip
59303  * @extends Ext.tip.ToolTip
59304  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
59305  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager documentation for additional usage details and examples.
59306  * @xtype quicktip
59307  */
59308 Ext.define('Ext.tip.QuickTip', {
59309     extend: 'Ext.tip.ToolTip',
59310     alternateClassName: 'Ext.QuickTip',
59311     /**
59312      * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
59313      */
59314     /**
59315      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
59316      */
59317     interceptTitles : false,
59318
59319     // Force creation of header Component
59320     title: '&#160;',
59321
59322     // private
59323     tagConfig : {
59324         namespace : "data-",
59325         attribute : "qtip",
59326         width : "qwidth",
59327         target : "target",
59328         title : "qtitle",
59329         hide : "hide",
59330         cls : "qclass",
59331         align : "qalign",
59332         anchor : "anchor"
59333     },
59334
59335     // private
59336     initComponent : function(){
59337         var me = this;
59338
59339         me.target = me.target || Ext.getDoc();
59340         me.targets = me.targets || {};
59341         me.callParent();
59342     },
59343
59344     /**
59345      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
59346      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
59347      * <div class="mdetail-params"><ul>
59348      * <li>autoHide</li>
59349      * <li>cls</li>
59350      * <li>dismissDelay (overrides the singleton value)</li>
59351      * <li>target (required)</li>
59352      * <li>text (required)</li>
59353      * <li>title</li>
59354      * <li>width</li></ul></div>
59355      * @param {Object} config The config object
59356      */
59357     register : function(config){
59358         var configs = Ext.isArray(config) ? config : arguments,
59359             i = 0,
59360             len = configs.length,
59361             target, j, targetLen;
59362
59363         for (; i < len; i++) {
59364             config = configs[i];
59365             target = config.target;
59366             if (target) {
59367                 if (Ext.isArray(target)) {
59368                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
59369                         this.targets[Ext.id(target[j])] = config;
59370                     }
59371                 } else{
59372                     this.targets[Ext.id(target)] = config;
59373                 }
59374             }
59375         }
59376     },
59377
59378     /**
59379      * Removes this quick tip from its element and destroys it.
59380      * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59381      */
59382     unregister : function(el){
59383         delete this.targets[Ext.id(el)];
59384     },
59385
59386     /**
59387      * Hides a visible tip or cancels an impending show for a particular element.
59388      * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
59389      */
59390     cancelShow: function(el){
59391         var me = this,
59392             activeTarget = me.activeTarget;
59393
59394         el = Ext.get(el).dom;
59395         if (me.isVisible()) {
59396             if (activeTarget && activeTarget.el == el) {
59397                 me.hide();
59398             }
59399         } else if (activeTarget && activeTarget.el == el) {
59400             me.clearTimer('show');
59401         }
59402     },
59403
59404     /**
59405      * @private
59406      * Reads the tip text from the closest node to the event target which contains the attribute we
59407      * are configured to look for. Returns an object containing the text from the attribute, and the target element from
59408      * which the text was read.
59409      */
59410     getTipCfg: function(e) {
59411         var t = e.getTarget(),
59412             titleText = t.title,
59413             cfg;
59414
59415         if (this.interceptTitles && titleText && Ext.isString(titleText)) {
59416             t.qtip = titleText;
59417             t.removeAttribute("title");
59418             e.preventDefault();
59419             return {
59420                 text: titleText
59421             };
59422         }
59423         else {
59424             cfg = this.tagConfig;
59425             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
59426             if (t) {
59427                 return {
59428                     target: t,
59429                     text: t.getAttribute(cfg.namespace + cfg.attribute)
59430                 };
59431             }
59432         }
59433     },
59434
59435     // private
59436     onTargetOver : function(e){
59437         var me = this,
59438             target = e.getTarget(),
59439             elTarget,
59440             cfg,
59441             ns,
59442             tipConfig,
59443             autoHide;
59444
59445         if (me.disabled) {
59446             return;
59447         }
59448
59449         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
59450         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
59451         // that smashed Ext.EventObject.
59452         me.targetXY = e.getXY();
59453
59454         if(!target || target.nodeType !== 1 || target == document || target == document.body){
59455             return;
59456         }
59457
59458         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
59459             me.clearTimer('hide');
59460             me.show();
59461             return;
59462         }
59463
59464         if (target) {
59465             Ext.Object.each(me.targets, function(key, value) {
59466                 var targetEl = Ext.fly(value.target);
59467                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
59468                     elTarget = targetEl.dom;
59469                     return false;
59470                 }
59471             });
59472             if (elTarget) {
59473                 me.activeTarget = me.targets[elTarget.id];
59474                 me.activeTarget.el = target;
59475                 me.anchor = me.activeTarget.anchor;
59476                 if (me.anchor) {
59477                     me.anchorTarget = target;
59478                 }
59479                 me.delayShow();
59480                 return;
59481             }
59482         }
59483
59484         elTarget = Ext.get(target);
59485         cfg = me.tagConfig;
59486         ns = cfg.namespace;
59487         tipConfig = me.getTipCfg(e);
59488
59489         if (tipConfig) {
59490
59491             // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
59492             // Change our target element to match that from which the tip text attribute was read.
59493             if (tipConfig.target) {
59494                 target = tipConfig.target;
59495                 elTarget = Ext.get(target);
59496             }
59497             autoHide = elTarget.getAttribute(ns + cfg.hide);
59498
59499             me.activeTarget = {
59500                 el: target,
59501                 text: tipConfig.text,
59502                 width: +elTarget.getAttribute(ns + cfg.width) || null,
59503                 autoHide: autoHide != "user" && autoHide !== 'false',
59504                 title: elTarget.getAttribute(ns + cfg.title),
59505                 cls: elTarget.getAttribute(ns + cfg.cls),
59506                 align: elTarget.getAttribute(ns + cfg.align)
59507
59508             };
59509             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
59510             if (me.anchor) {
59511                 me.anchorTarget = target;
59512             }
59513             me.delayShow();
59514         }
59515     },
59516
59517     // private
59518     onTargetOut : function(e){
59519         var me = this;
59520
59521         // If moving within the current target, and it does not have a new tip, ignore the mouseout
59522         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
59523             return;
59524         }
59525
59526         me.clearTimer('show');
59527         if (me.autoHide !== false) {
59528             me.delayHide();
59529         }
59530     },
59531
59532     // inherit docs
59533     showAt : function(xy){
59534         var me = this,
59535             target = me.activeTarget;
59536
59537         if (target) {
59538             if (!me.rendered) {
59539                 me.render(Ext.getBody());
59540                 me.activeTarget = target;
59541             }
59542             if (target.title) {
59543                 me.setTitle(target.title || '');
59544                 me.header.show();
59545             } else {
59546                 me.header.hide();
59547             }
59548             me.body.update(target.text);
59549             me.autoHide = target.autoHide;
59550             me.dismissDelay = target.dismissDelay || me.dismissDelay;
59551             if (me.lastCls) {
59552                 me.el.removeCls(me.lastCls);
59553                 delete me.lastCls;
59554             }
59555             if (target.cls) {
59556                 me.el.addCls(target.cls);
59557                 me.lastCls = target.cls;
59558             }
59559
59560             me.setWidth(target.width);
59561
59562             if (me.anchor) {
59563                 me.constrainPosition = false;
59564             } else if (target.align) { // TODO: this doesn't seem to work consistently
59565                 xy = me.el.getAlignToXY(target.el, target.align);
59566                 me.constrainPosition = false;
59567             }else{
59568                 me.constrainPosition = true;
59569             }
59570         }
59571         me.callParent([xy]);
59572     },
59573
59574     // inherit docs
59575     hide: function(){
59576         delete this.activeTarget;
59577         this.callParent();
59578     }
59579 });
59580
59581 /**
59582  * @class Ext.tip.QuickTipManager
59583  *
59584  * Provides attractive and customizable tooltips for any element. The QuickTips
59585  * singleton is used to configure and manage tooltips globally for multiple elements
59586  * in a generic manner.  To create individual tooltips with maximum customizability,
59587  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
59588  *
59589  * Quicktips can be configured via tag attributes directly in markup, or by
59590  * registering quick tips programmatically via the {@link #register} method.
59591  *
59592  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
59593  * {@link #getQuickTip}, and supports all the methods, and all the all the
59594  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
59595  * tooltips shown by the singleton.
59596  *
59597  * Below is the summary of the configuration properties which can be used.
59598  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
59599  *
59600  * ## QuickTips singleton configs (all are optional)
59601  *
59602  *  - `dismissDelay`
59603  *  - `hideDelay`
59604  *  - `maxWidth`
59605  *  - `minWidth`
59606  *  - `showDelay`
59607  *  - `trackMouse`
59608  *
59609  * ## Target element configs (optional unless otherwise noted)
59610  *
59611  *  - `autoHide`
59612  *  - `cls`
59613  *  - `dismissDelay` (overrides singleton value)
59614  *  - `target` (required)
59615  *  - `text` (required)
59616  *  - `title`
59617  *  - `width`
59618  *
59619  * Here is an example showing how some of these config options could be used:
59620  *
59621  *     @example
59622  *     // Init the singleton.  Any tag-based quick tips will start working.
59623  *     Ext.tip.QuickTipManager.init();
59624  *
59625  *     // Apply a set of config properties to the singleton
59626  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
59627  *         maxWidth: 200,
59628  *         minWidth: 100,
59629  *         showDelay: 50      // Show 50ms after entering target
59630  *     });
59631  *
59632  *     // Create a small panel to add a quick tip to
59633  *     Ext.create('Ext.container.Container', {
59634  *         id: 'quickTipContainer',
59635  *         width: 200,
59636  *         height: 150,
59637  *         style: {
59638  *             backgroundColor:'#000000'
59639  *         },
59640  *         renderTo: Ext.getBody()
59641  *     });
59642  *
59643  *
59644  *     // Manually register a quick tip for a specific element
59645  *     Ext.tip.QuickTipManager.register({
59646  *         target: 'quickTipContainer',
59647  *         title: 'My Tooltip',
59648  *         text: 'This tooltip was added in code',
59649  *         width: 100,
59650  *         dismissDelay: 10000 // Hide after 10 seconds hover
59651  *     });
59652  *
59653  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
59654  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
59655  * of supported attributes (optional unless otherwise noted):
59656  *
59657  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
59658  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
59659  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
59660  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
59661  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
59662  *
59663  * Here is an example of configuring an HTML element to display a tooltip from markup:
59664  *
59665  *     // Add a quick tip to an HTML button
59666  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
59667  *          data-qtip="This is a quick tip from markup!"></input>
59668  *
59669  * @singleton
59670  */
59671 Ext.define('Ext.tip.QuickTipManager', function() {
59672     var tip,
59673         disabled = false;
59674
59675     return {
59676         requires: ['Ext.tip.QuickTip'],
59677         singleton: true,
59678         alternateClassName: 'Ext.QuickTips',
59679
59680         /**
59681          * Initialize the global QuickTips instance and prepare any quick tips.
59682          * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
59683          * preload images. (Defaults to true)
59684          * @param {Object} config (optional) config object for the created QuickTip. By
59685          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
59686          * be changed by supplying an xtype property or a className property in this object.
59687          * All other properties on this object are configuration for the created component.
59688          */
59689         init : function (autoRender, config) {
59690             if (!tip) {
59691                 if (!Ext.isReady) {
59692                     Ext.onReady(function(){
59693                         Ext.tip.QuickTipManager.init(autoRender);
59694                     });
59695                     return;
59696                 }
59697
59698                 var tipConfig = Ext.apply({ disabled: disabled }, config),
59699                     className = tipConfig.className,
59700                     xtype = tipConfig.xtype;
59701
59702                 if (className) {
59703                     delete tipConfig.className;
59704                 } else if (xtype) {
59705                     className = 'widget.' + xtype;
59706                     delete tipConfig.xtype;
59707                 }
59708
59709                 if (autoRender !== false) {
59710                     tipConfig.renderTo = document.body;
59711
59712                     if (tipConfig.renderTo.tagName != 'BODY') { // e.g., == 'FRAMESET'
59713                         Ext.Error.raise({
59714                             sourceClass: 'Ext.tip.QuickTipManager',
59715                             sourceMethod: 'init',
59716                             msg: 'Cannot init QuickTipManager: no document body'
59717                         });
59718                     }
59719                 }
59720
59721                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
59722             }
59723         },
59724
59725         /**
59726          * Destroy the QuickTips instance.
59727          */
59728         destroy: function() {
59729             if (tip) {
59730                 var undef;
59731                 tip.destroy();
59732                 tip = undef;
59733             }
59734         },
59735
59736         // Protected method called by the dd classes
59737         ddDisable : function(){
59738             // don't disable it if we don't need to
59739             if(tip && !disabled){
59740                 tip.disable();
59741             }
59742         },
59743
59744         // Protected method called by the dd classes
59745         ddEnable : function(){
59746             // only enable it if it hasn't been disabled
59747             if(tip && !disabled){
59748                 tip.enable();
59749             }
59750         },
59751
59752         /**
59753          * Enable quick tips globally.
59754          */
59755         enable : function(){
59756             if(tip){
59757                 tip.enable();
59758             }
59759             disabled = false;
59760         },
59761
59762         /**
59763          * Disable quick tips globally.
59764          */
59765         disable : function(){
59766             if(tip){
59767                 tip.disable();
59768             }
59769             disabled = true;
59770         },
59771
59772         /**
59773          * Returns true if quick tips are enabled, else false.
59774          * @return {Boolean}
59775          */
59776         isEnabled : function(){
59777             return tip !== undefined && !tip.disabled;
59778         },
59779
59780         /**
59781          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
59782          * @return {Ext.tip.QuickTip}
59783          */
59784         getQuickTip : function(){
59785             return tip;
59786         },
59787
59788         /**
59789          * Configures a new quick tip instance and assigns it to a target element.  See
59790          * {@link Ext.tip.QuickTip#register} for details.
59791          * @param {Object} config The config object
59792          */
59793         register : function(){
59794             tip.register.apply(tip, arguments);
59795         },
59796
59797         /**
59798          * Removes any registered quick tip from the target element and destroys it.
59799          * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59800          */
59801         unregister : function(){
59802             tip.unregister.apply(tip, arguments);
59803         },
59804
59805         /**
59806          * Alias of {@link #register}.
59807          * @param {Object} config The config object
59808          */
59809         tips : function(){
59810             tip.register.apply(tip, arguments);
59811         }
59812     };
59813 }());
59814 /**
59815  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
59816  * A typical Ext.app.Application might look like this:
59817  *
59818  *     Ext.application({
59819  *         name: 'MyApp',
59820  *         launch: function() {
59821  *             Ext.create('Ext.container.Viewport', {
59822  *                 items: {
59823  *                     html: 'My App'
59824  *                 }
59825  *             });
59826  *         }
59827  *     });
59828  *
59829  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
59830  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
59831  * of colliding global variables.
59832  *
59833  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
59834  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
59835  * the example above.
59836  *
59837  * # Telling Application about the rest of the app
59838  *
59839  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
59840  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
59841  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
59842  * Here's how we'd tell our Application about all these things:
59843  *
59844  *     Ext.application({
59845  *         name: 'Blog',
59846  *         models: ['Post', 'Comment'],
59847  *         controllers: ['Posts', 'Comments'],
59848  *
59849  *         launch: function() {
59850  *             ...
59851  *         }
59852  *     });
59853  *
59854  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
59855  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
59856  * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
59857  * in this case expecting the controllers to reside in `app/controller/Posts.js` and
59858  * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
59859  * automatically loaded. Here's how our Posts controller like be defined:
59860  *
59861  *     Ext.define('MyApp.controller.Posts', {
59862  *         extend: 'Ext.app.Controller',
59863  *         views: ['posts.List', 'posts.Edit'],
59864  *
59865  *         //the rest of the Controller here
59866  *     });
59867  *
59868  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
59869  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
59870  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
59871  * application using the Ext JS 4 SDK Tools.
59872  *
59873  * For more information about writing Ext JS 4 applications, please see the
59874  * [application architecture guide][mvc].
59875  *
59876  * [mvc]: #!/guide/application_architecture
59877  *
59878  * @docauthor Ed Spencer
59879  */
59880 Ext.define('Ext.app.Application', {
59881     extend: 'Ext.app.Controller',
59882
59883     requires: [
59884         'Ext.ModelManager',
59885         'Ext.data.Model',
59886         'Ext.data.StoreManager',
59887         'Ext.tip.QuickTipManager',
59888         'Ext.ComponentManager',
59889         'Ext.app.EventBus'
59890     ],
59891
59892     /**
59893      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
59894      * models and stores. Don't use spaces or special characters in the name.
59895      */
59896
59897     /**
59898      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
59899      * instance.
59900      */
59901     scope: undefined,
59902
59903     /**
59904      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
59905      */
59906     enableQuickTips: true,
59907
59908     /**
59909      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
59910      */
59911
59912     /**
59913      * @cfg {String} appFolder The path to the directory which contains all application's classes.
59914      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
59915      */
59916     appFolder: 'app',
59917
59918     /**
59919      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
59920      * before firing the launch function.
59921      */
59922     autoCreateViewport: false,
59923
59924     /**
59925      * Creates new Application.
59926      * @param {Object} [config] Config object.
59927      */
59928     constructor: function(config) {
59929         config = config || {};
59930         Ext.apply(this, config);
59931
59932         var requires = config.requires || [];
59933
59934         Ext.Loader.setPath(this.name, this.appFolder);
59935
59936         if (this.paths) {
59937             Ext.Object.each(this.paths, function(key, value) {
59938                 Ext.Loader.setPath(key, value);
59939             });
59940         }
59941
59942         this.callParent(arguments);
59943
59944         this.eventbus = Ext.create('Ext.app.EventBus');
59945
59946         var controllers = Ext.Array.from(this.controllers),
59947             ln = controllers && controllers.length,
59948             i, controller;
59949
59950         this.controllers = Ext.create('Ext.util.MixedCollection');
59951
59952         if (this.autoCreateViewport) {
59953             requires.push(this.getModuleClassName('Viewport', 'view'));
59954         }
59955
59956         for (i = 0; i < ln; i++) {
59957             requires.push(this.getModuleClassName(controllers[i], 'controller'));
59958         }
59959
59960         Ext.require(requires);
59961
59962         Ext.onReady(function() {
59963             for (i = 0; i < ln; i++) {
59964                 controller = this.getController(controllers[i]);
59965                 controller.init(this);
59966             }
59967
59968             this.onBeforeLaunch.call(this);
59969         }, this);
59970     },
59971
59972     control: function(selectors, listeners, controller) {
59973         this.eventbus.control(selectors, listeners, controller);
59974     },
59975
59976     /**
59977      * Called automatically when the page has completely loaded. This is an empty function that should be
59978      * overridden by each application that needs to take action on page load
59979      * @property launch
59980      * @type Function
59981      * @param {String} profile The detected {@link #profiles application profile}
59982      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
59983      * action immediately after running the launch function. Return false to prevent this behavior.
59984      */
59985     launch: Ext.emptyFn,
59986
59987     /**
59988      * @private
59989      */
59990     onBeforeLaunch: function() {
59991         if (this.enableQuickTips) {
59992             Ext.tip.QuickTipManager.init();
59993         }
59994
59995         if (this.autoCreateViewport) {
59996             this.getView('Viewport').create();
59997         }
59998
59999         this.launch.call(this.scope || this);
60000         this.launched = true;
60001         this.fireEvent('launch', this);
60002
60003         this.controllers.each(function(controller) {
60004             controller.onLaunch(this);
60005         }, this);
60006     },
60007
60008     getModuleClassName: function(name, type) {
60009         var namespace = Ext.Loader.getPrefix(name);
60010
60011         if (namespace.length > 0 && namespace !== name) {
60012             return name;
60013         }
60014
60015         return this.name + '.' + type + '.' + name;
60016     },
60017
60018     getController: function(name) {
60019         var controller = this.controllers.get(name);
60020
60021         if (!controller) {
60022             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
60023                 application: this,
60024                 id: name
60025             });
60026
60027             this.controllers.add(controller);
60028         }
60029
60030         return controller;
60031     },
60032
60033     getStore: function(name) {
60034         var store = Ext.StoreManager.get(name);
60035
60036         if (!store) {
60037             store = Ext.create(this.getModuleClassName(name, 'store'), {
60038                 storeId: name
60039             });
60040         }
60041
60042         return store;
60043     },
60044
60045     getModel: function(model) {
60046         model = this.getModuleClassName(model, 'model');
60047
60048         return Ext.ModelManager.getModel(model);
60049     },
60050
60051     getView: function(view) {
60052         view = this.getModuleClassName(view, 'view');
60053
60054         return Ext.ClassManager.get(view);
60055     }
60056 });
60057
60058 /**
60059  * @class Ext.chart.Callout
60060  * A mixin providing callout functionality for Ext.chart.series.Series.
60061  */
60062 Ext.define('Ext.chart.Callout', {
60063
60064     /* Begin Definitions */
60065
60066     /* End Definitions */
60067
60068     constructor: function(config) {
60069         if (config.callouts) {
60070             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
60071                 color: "#000",
60072                 font: "11px Helvetica, sans-serif"
60073             });
60074             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
60075             this.calloutsArray = [];
60076         }
60077     },
60078
60079     renderCallouts: function() {
60080         if (!this.callouts) {
60081             return;
60082         }
60083
60084         var me = this,
60085             items = me.items,
60086             animate = me.chart.animate,
60087             config = me.callouts,
60088             styles = config.styles,
60089             group = me.calloutsArray,
60090             store = me.chart.store,
60091             len = store.getCount(),
60092             ratio = items.length / len,
60093             previouslyPlacedCallouts = [],
60094             i,
60095             count,
60096             j,
60097             p;
60098             
60099         for (i = 0, count = 0; i < len; i++) {
60100             for (j = 0; j < ratio; j++) {
60101                 var item = items[count],
60102                     label = group[count],
60103                     storeItem = store.getAt(i),
60104                     display;
60105                 
60106                 display = config.filter(storeItem);
60107                 
60108                 if (!display && !label) {
60109                     count++;
60110                     continue;               
60111                 }
60112                 
60113                 if (!label) {
60114                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
60115                 }
60116                 for (p in label) {
60117                     if (label[p] && label[p].setAttributes) {
60118                         label[p].setAttributes(styles, true);
60119                     }
60120                 }
60121                 if (!display) {
60122                     for (p in label) {
60123                         if (label[p]) {
60124                             if (label[p].setAttributes) {
60125                                 label[p].setAttributes({
60126                                     hidden: true
60127                                 }, true);
60128                             } else if(label[p].setVisible) {
60129                                 label[p].setVisible(false);
60130                             }
60131                         }
60132                     }
60133                 }
60134                 config.renderer(label, storeItem);
60135                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
60136                                   j, count, previouslyPlacedCallouts);
60137                 previouslyPlacedCallouts.push(label);
60138                 count++;
60139             }
60140         }
60141         this.hideCallouts(count);
60142     },
60143
60144     onCreateCallout: function(storeItem, item, i, display) {
60145         var me = this,
60146             group = me.calloutsGroup,
60147             config = me.callouts,
60148             styles = config.styles,
60149             width = styles.width,
60150             height = styles.height,
60151             chart = me.chart,
60152             surface = chart.surface,
60153             calloutObj = {
60154                 //label: false,
60155                 //box: false,
60156                 lines: false
60157             };
60158
60159         calloutObj.lines = surface.add(Ext.apply({},
60160         {
60161             type: 'path',
60162             path: 'M0,0',
60163             stroke: me.getLegendColor() || '#555'
60164         },
60165         styles));
60166
60167         if (config.items) {
60168             calloutObj.panel = Ext.create('widget.panel', {
60169                 style: "position: absolute;",    
60170                 width: width,
60171                 height: height,
60172                 items: config.items,
60173                 renderTo: chart.el
60174             });
60175         }
60176
60177         return calloutObj;
60178     },
60179
60180     hideCallouts: function(index) {
60181         var calloutsArray = this.calloutsArray,
60182             len = calloutsArray.length,
60183             co,
60184             p;
60185         while (len-->index) {
60186             co = calloutsArray[len];
60187             for (p in co) {
60188                 if (co[p]) {
60189                     co[p].hide(true);
60190                 }
60191             }
60192         }
60193     }
60194 });
60195
60196 /**
60197  * @class Ext.draw.CompositeSprite
60198  * @extends Ext.util.MixedCollection
60199  *
60200  * A composite Sprite handles a group of sprites with common methods to a sprite
60201  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
60202  * added to the group.
60203  *
60204  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
60205  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
60206  *
60207  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
60208  * rendered:
60209  *
60210  *     var group = Ext.create('Ext.draw.CompositeSprite', {
60211  *         surface: drawComponent.surface
60212  *     });
60213  *                  
60214  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
60215  *  
60216  *     group.add(sprite1);
60217  *     group.add(sprite2);
60218  *     group.add(sprite3);
60219  *                  
60220  * And then apply common Sprite methods to them:
60221  *  
60222  *     group.setAttributes({
60223  *         fill: '#f00'
60224  *     }, true);
60225  */
60226 Ext.define('Ext.draw.CompositeSprite', {
60227
60228     /* Begin Definitions */
60229
60230     extend: 'Ext.util.MixedCollection',
60231     mixins: {
60232         animate: 'Ext.util.Animate'
60233     },
60234
60235     /* End Definitions */
60236     isCompositeSprite: true,
60237     constructor: function(config) {
60238         var me = this;
60239         
60240         config = config || {};
60241         Ext.apply(me, config);
60242
60243         me.addEvents(
60244             'mousedown',
60245             'mouseup',
60246             'mouseover',
60247             'mouseout',
60248             'click'
60249         );
60250         me.id = Ext.id(null, 'ext-sprite-group-');
60251         me.callParent();
60252     },
60253
60254     // @private
60255     onClick: function(e) {
60256         this.fireEvent('click', e);
60257     },
60258
60259     // @private
60260     onMouseUp: function(e) {
60261         this.fireEvent('mouseup', e);
60262     },
60263
60264     // @private
60265     onMouseDown: function(e) {
60266         this.fireEvent('mousedown', e);
60267     },
60268
60269     // @private
60270     onMouseOver: function(e) {
60271         this.fireEvent('mouseover', e);
60272     },
60273
60274     // @private
60275     onMouseOut: function(e) {
60276         this.fireEvent('mouseout', e);
60277     },
60278
60279     attachEvents: function(o) {
60280         var me = this;
60281         
60282         o.on({
60283             scope: me,
60284             mousedown: me.onMouseDown,
60285             mouseup: me.onMouseUp,
60286             mouseover: me.onMouseOver,
60287             mouseout: me.onMouseOut,
60288             click: me.onClick
60289         });
60290     },
60291
60292     // Inherit docs from MixedCollection
60293     add: function(key, o) {
60294         var result = this.callParent(arguments);
60295         this.attachEvents(result);
60296         return result;
60297     },
60298
60299     insert: function(index, key, o) {
60300         return this.callParent(arguments);
60301     },
60302
60303     // Inherit docs from MixedCollection
60304     remove: function(o) {
60305         var me = this;
60306         
60307         o.un({
60308             scope: me,
60309             mousedown: me.onMouseDown,
60310             mouseup: me.onMouseUp,
60311             mouseover: me.onMouseOver,
60312             mouseout: me.onMouseOut,
60313             click: me.onClick
60314         });
60315         return me.callParent(arguments);
60316     },
60317     
60318     /**
60319      * Returns the group bounding box.
60320      * Behaves like {@link Ext.draw.Sprite#getBBox} method.
60321      * @return {Object} an object with x, y, width, and height properties.
60322      */
60323     getBBox: function() {
60324         var i = 0,
60325             sprite,
60326             bb,
60327             items = this.items,
60328             len = this.length,
60329             infinity = Infinity,
60330             minX = infinity,
60331             maxHeight = -infinity,
60332             minY = infinity,
60333             maxWidth = -infinity,
60334             maxWidthBBox, maxHeightBBox;
60335         
60336         for (; i < len; i++) {
60337             sprite = items[i];
60338             if (sprite.el) {
60339                 bb = sprite.getBBox();
60340                 minX = Math.min(minX, bb.x);
60341                 minY = Math.min(minY, bb.y);
60342                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
60343                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
60344             }
60345         }
60346         
60347         return {
60348             x: minX,
60349             y: minY,
60350             height: maxHeight - minY,
60351             width: maxWidth - minX
60352         };
60353     },
60354
60355     /**
60356      * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
60357      * provides a description of the attributes that can be set with this method.
60358      * @param {Object} attrs Attributes to be changed on the sprite.
60359      * @param {Boolean} redraw Flag to immediatly draw the change.
60360      * @return {Ext.draw.CompositeSprite} this
60361      */
60362     setAttributes: function(attrs, redraw) {
60363         var i = 0,
60364             items = this.items,
60365             len = this.length;
60366             
60367         for (; i < len; i++) {
60368             items[i].setAttributes(attrs, redraw);
60369         }
60370         return this;
60371     },
60372
60373     /**
60374      * Hides all sprites. If the first parameter of the method is true
60375      * then a redraw will be forced for each sprite.
60376      * @param {Boolean} redraw Flag to immediatly draw the change.
60377      * @return {Ext.draw.CompositeSprite} this
60378      */
60379     hide: function(redraw) {
60380         var i = 0,
60381             items = this.items,
60382             len = this.length;
60383             
60384         for (; i < len; i++) {
60385             items[i].hide(redraw);
60386         }
60387         return this;
60388     },
60389
60390     /**
60391      * Shows all sprites. If the first parameter of the method is true
60392      * then a redraw will be forced for each sprite.
60393      * @param {Boolean} redraw Flag to immediatly draw the change.
60394      * @return {Ext.draw.CompositeSprite} this
60395      */
60396     show: function(redraw) {
60397         var i = 0,
60398             items = this.items,
60399             len = this.length;
60400             
60401         for (; i < len; i++) {
60402             items[i].show(redraw);
60403         }
60404         return this;
60405     },
60406
60407     redraw: function() {
60408         var me = this,
60409             i = 0,
60410             items = me.items,
60411             surface = me.getSurface(),
60412             len = me.length;
60413         
60414         if (surface) {
60415             for (; i < len; i++) {
60416                 surface.renderItem(items[i]);
60417             }
60418         }
60419         return me;
60420     },
60421
60422     setStyle: function(obj) {
60423         var i = 0,
60424             items = this.items,
60425             len = this.length,
60426             item, el;
60427             
60428         for (; i < len; i++) {
60429             item = items[i];
60430             el = item.el;
60431             if (el) {
60432                 el.setStyle(obj);
60433             }
60434         }
60435     },
60436
60437     addCls: function(obj) {
60438         var i = 0,
60439             items = this.items,
60440             surface = this.getSurface(),
60441             len = this.length;
60442         
60443         if (surface) {
60444             for (; i < len; i++) {
60445                 surface.addCls(items[i], obj);
60446             }
60447         }
60448     },
60449
60450     removeCls: function(obj) {
60451         var i = 0,
60452             items = this.items,
60453             surface = this.getSurface(),
60454             len = this.length;
60455         
60456         if (surface) {
60457             for (; i < len; i++) {
60458                 surface.removeCls(items[i], obj);
60459             }
60460         }
60461     },
60462     
60463     /**
60464      * Grab the surface from the items
60465      * @private
60466      * @return {Ext.draw.Surface} The surface, null if not found
60467      */
60468     getSurface: function(){
60469         var first = this.first();
60470         if (first) {
60471             return first.surface;
60472         }
60473         return null;
60474     },
60475     
60476     /**
60477      * Destroys the SpriteGroup
60478      */
60479     destroy: function(){
60480         var me = this,
60481             surface = me.getSurface(),
60482             item;
60483             
60484         if (surface) {
60485             while (me.getCount() > 0) {
60486                 item = me.first();
60487                 me.remove(item);
60488                 surface.remove(item);
60489             }
60490         }
60491         me.clearListeners();
60492     }
60493 });
60494
60495 /**
60496  * @class Ext.layout.component.Auto
60497  * @extends Ext.layout.component.Component
60498  * @private
60499  *
60500  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
60501  * render any child Elements when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured.</p>
60502  */
60503
60504 Ext.define('Ext.layout.component.Auto', {
60505
60506     /* Begin Definitions */
60507
60508     alias: 'layout.autocomponent',
60509
60510     extend: 'Ext.layout.component.Component',
60511
60512     /* End Definitions */
60513
60514     type: 'autocomponent',
60515
60516     onLayout : function(width, height) {
60517         this.setTargetSize(width, height);
60518     }
60519 });
60520 /**
60521  * @class Ext.chart.theme.Theme
60522  * 
60523  * Provides chart theming.
60524  * 
60525  * Used as mixins by Ext.chart.Chart.
60526  */
60527 Ext.define('Ext.chart.theme.Theme', {
60528
60529     /* Begin Definitions */
60530
60531     requires: ['Ext.draw.Color'],
60532
60533     /* End Definitions */
60534
60535     theme: 'Base',
60536     themeAttrs: false,
60537     
60538     initTheme: function(theme) {
60539         var me = this,
60540             themes = Ext.chart.theme,
60541             key, gradients;
60542         if (theme) {
60543             theme = theme.split(':');
60544             for (key in themes) {
60545                 if (key == theme[0]) {
60546                     gradients = theme[1] == 'gradients';
60547                     me.themeAttrs = new themes[key]({
60548                         useGradients: gradients
60549                     });
60550                     if (gradients) {
60551                         me.gradients = me.themeAttrs.gradients;
60552                     }
60553                     if (me.themeAttrs.background) {
60554                         me.background = me.themeAttrs.background;
60555                     }
60556                     return;
60557                 }
60558             }
60559             Ext.Error.raise('No theme found named "' + theme + '"');
60560         }
60561     }
60562 }, 
60563 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
60564 function() {
60565    /* Theme constructor: takes either a complex object with styles like:
60566   
60567    {
60568         axis: {
60569             fill: '#000',
60570             'stroke-width': 1
60571         },
60572         axisLabelTop: {
60573             fill: '#000',
60574             font: '11px Arial'
60575         },
60576         axisLabelLeft: {
60577             fill: '#000',
60578             font: '11px Arial'
60579         },
60580         axisLabelRight: {
60581             fill: '#000',
60582             font: '11px Arial'
60583         },
60584         axisLabelBottom: {
60585             fill: '#000',
60586             font: '11px Arial'
60587         },
60588         axisTitleTop: {
60589             fill: '#000',
60590             font: '11px Arial'
60591         },
60592         axisTitleLeft: {
60593             fill: '#000',
60594             font: '11px Arial'
60595         },
60596         axisTitleRight: {
60597             fill: '#000',
60598             font: '11px Arial'
60599         },
60600         axisTitleBottom: {
60601             fill: '#000',
60602             font: '11px Arial'
60603         },
60604         series: {
60605             'stroke-width': 1
60606         },
60607         seriesLabel: {
60608             font: '12px Arial',
60609             fill: '#333'
60610         },
60611         marker: {
60612             stroke: '#555',
60613             fill: '#000',
60614             radius: 3,
60615             size: 3
60616         },
60617         seriesThemes: [{
60618             fill: '#C6DBEF'
60619         }, {
60620             fill: '#9ECAE1'
60621         }, {
60622             fill: '#6BAED6'
60623         }, {
60624             fill: '#4292C6'
60625         }, {
60626             fill: '#2171B5'
60627         }, {
60628             fill: '#084594'
60629         }],
60630         markerThemes: [{
60631             fill: '#084594',
60632             type: 'circle' 
60633         }, {
60634             fill: '#2171B5',
60635             type: 'cross'
60636         }, {
60637             fill: '#4292C6',
60638             type: 'plus'
60639         }]
60640     }
60641   
60642   ...or also takes just an array of colors and creates the complex object:
60643   
60644   {
60645       colors: ['#aaa', '#bcd', '#eee']
60646   }
60647   
60648   ...or takes just a base color and makes a theme from it
60649   
60650   {
60651       baseColor: '#bce'
60652   }
60653   
60654   To create a new theme you may add it to the Themes object:
60655   
60656   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
60657       constructor: function(config) {
60658           Ext.chart.theme.call(this, config, {
60659               baseColor: '#mybasecolor'
60660           });
60661       }
60662   });
60663   
60664   //Proposal:
60665   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
60666   
60667   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
60668   
60669   {
60670       theme: 'mynewtheme'
60671   }
60672  */
60673
60674 (function() {
60675     Ext.chart.theme = function(config, base) {
60676         config = config || {};
60677         var i = 0, l, colors, color,
60678             seriesThemes, markerThemes,
60679             seriesTheme, markerTheme, 
60680             key, gradients = [],
60681             midColor, midL;
60682         
60683         if (config.baseColor) {
60684             midColor = Ext.draw.Color.fromString(config.baseColor);
60685             midL = midColor.getHSL()[2];
60686             if (midL < 0.15) {
60687                 midColor = midColor.getLighter(0.3);
60688             } else if (midL < 0.3) {
60689                 midColor = midColor.getLighter(0.15);
60690             } else if (midL > 0.85) {
60691                 midColor = midColor.getDarker(0.3);
60692             } else if (midL > 0.7) {
60693                 midColor = midColor.getDarker(0.15);
60694             }
60695             config.colors = [ midColor.getDarker(0.3).toString(),
60696                               midColor.getDarker(0.15).toString(),
60697                               midColor.toString(),
60698                               midColor.getLighter(0.15).toString(),
60699                               midColor.getLighter(0.3).toString()];
60700
60701             delete config.baseColor;
60702         }
60703         if (config.colors) {
60704             colors = config.colors.slice();
60705             markerThemes = base.markerThemes;
60706             seriesThemes = base.seriesThemes;
60707             l = colors.length;
60708             base.colors = colors;
60709             for (; i < l; i++) {
60710                 color = colors[i];
60711                 markerTheme = markerThemes[i] || {};
60712                 seriesTheme = seriesThemes[i] || {};
60713                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
60714                 markerThemes[i] = markerTheme;
60715                 seriesThemes[i] = seriesTheme;
60716             }
60717             base.markerThemes = markerThemes.slice(0, l);
60718             base.seriesThemes = seriesThemes.slice(0, l);
60719         //the user is configuring something in particular (either markers, series or pie slices)
60720         }
60721         for (key in base) {
60722             if (key in config) {
60723                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
60724                     Ext.apply(base[key], config[key]);
60725                 } else {
60726                     base[key] = config[key];
60727                 }
60728             }
60729         }
60730         if (config.useGradients) {
60731             colors = base.colors || (function () {
60732                 var ans = [];
60733                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
60734                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
60735                 }
60736                 return ans;
60737             })();
60738             for (i = 0, l = colors.length; i < l; i++) {
60739                 midColor = Ext.draw.Color.fromString(colors[i]);
60740                 if (midColor) {
60741                     color = midColor.getDarker(0.1).toString();
60742                     midColor = midColor.toString();
60743                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
60744                     gradients.push({
60745                         id: key,
60746                         angle: 45,
60747                         stops: {
60748                             0: {
60749                                 color: midColor.toString()
60750                             },
60751                             100: {
60752                                 color: color.toString()
60753                             }
60754                         }
60755                     });
60756                     colors[i] = 'url(#' + key + ')'; 
60757                 }
60758             }
60759             base.gradients = gradients;
60760             base.colors = colors;
60761         }
60762         /*
60763         base.axis = Ext.apply(base.axis || {}, config.axis || {});
60764         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
60765         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
60766         */
60767         Ext.apply(this, base);
60768     };
60769 })();
60770 });
60771
60772 /**
60773  * @class Ext.chart.Mask
60774  *
60775  * Defines a mask for a chart's series.
60776  * The 'chart' member must be set prior to rendering.
60777  *
60778  * A Mask can be used to select a certain region in a chart.
60779  * When enabled, the `select` event will be triggered when a
60780  * region is selected by the mask, allowing the user to perform
60781  * other tasks like zooming on that region, etc.
60782  *
60783  * In order to use the mask one has to set the Chart `mask` option to
60784  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
60785  * listener could be:
60786  *
60787         items: {
60788             xtype: 'chart',
60789             animate: true,
60790             store: store1,
60791             mask: 'horizontal',
60792             listeners: {
60793                 select: {
60794                     fn: function(me, selection) {
60795                         me.setZoom(selection);
60796                         me.mask.hide();
60797                     }
60798                 }
60799             },
60800
60801  * In this example we zoom the chart to that particular region. You can also get
60802  * a handle to a mask instance from the chart object. The `chart.mask` element is a
60803  * `Ext.Panel`.
60804  * 
60805  */
60806 Ext.define('Ext.chart.Mask', {
60807     require: ['Ext.chart.MaskLayer'],
60808     /**
60809      * Creates new Mask.
60810      * @param {Object} config (optional) Config object.
60811      */
60812     constructor: function(config) {
60813         var me = this;
60814
60815         me.addEvents('select');
60816
60817         if (config) {
60818             Ext.apply(me, config);
60819         }
60820         if (me.mask) {
60821             me.on('afterrender', function() {
60822                 //create a mask layer component
60823                 var comp = Ext.create('Ext.chart.MaskLayer', {
60824                     renderTo: me.el
60825                 });
60826                 comp.el.on({
60827                     'mousemove': function(e) {
60828                         me.onMouseMove(e);
60829                     },
60830                     'mouseup': function(e) {
60831                         me.resized(e);
60832                     }
60833                 });
60834                 //create a resize handler for the component
60835                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
60836                     el: comp.el,
60837                     handles: 'all',
60838                     pinned: true
60839                 });
60840                 resizeHandler.on({
60841                     'resize': function(e) {
60842                         me.resized(e);    
60843                     }    
60844                 });
60845                 comp.initDraggable();
60846                 me.maskType = me.mask;
60847                 me.mask = comp;
60848                 me.maskSprite = me.surface.add({
60849                     type: 'path',
60850                     path: ['M', 0, 0],
60851                     zIndex: 1001,
60852                     opacity: 0.7,
60853                     hidden: true,
60854                     stroke: '#444'
60855                 });
60856             }, me, { single: true });
60857         }
60858     },
60859     
60860     resized: function(e) {
60861         var me = this,
60862             bbox = me.bbox || me.chartBBox,
60863             x = bbox.x,
60864             y = bbox.y,
60865             width = bbox.width,
60866             height = bbox.height,
60867             box = me.mask.getBox(true),
60868             max = Math.max,
60869             min = Math.min,
60870             staticX = box.x - x,
60871             staticY = box.y - y;
60872         
60873         staticX = max(staticX, x);
60874         staticY = max(staticY, y);
60875         staticX = min(staticX, width);
60876         staticY = min(staticY, height);
60877         box.x = staticX;
60878         box.y = staticY;
60879         me.fireEvent('select', me, box);
60880     },
60881
60882     onMouseUp: function(e) {
60883         var me = this,
60884             bbox = me.bbox || me.chartBBox,
60885             sel = me.maskSelection;
60886         me.maskMouseDown = false;
60887         me.mouseDown = false;
60888         if (me.mouseMoved) {
60889             me.onMouseMove(e);
60890             me.mouseMoved = false;
60891             me.fireEvent('select', me, {
60892                 x: sel.x - bbox.x,
60893                 y: sel.y - bbox.y,
60894                 width: sel.width,
60895                 height: sel.height
60896             });
60897         }
60898     },
60899
60900     onMouseDown: function(e) {
60901         var me = this;
60902         me.mouseDown = true;
60903         me.mouseMoved = false;
60904         me.maskMouseDown = {
60905             x: e.getPageX() - me.el.getX(),
60906             y: e.getPageY() - me.el.getY()
60907         };
60908     },
60909
60910     onMouseMove: function(e) {
60911         var me = this,
60912             mask = me.maskType,
60913             bbox = me.bbox || me.chartBBox,
60914             x = bbox.x,
60915             y = bbox.y,
60916             math = Math,
60917             floor = math.floor,
60918             abs = math.abs,
60919             min = math.min,
60920             max = math.max,
60921             height = floor(y + bbox.height),
60922             width = floor(x + bbox.width),
60923             posX = e.getPageX(),
60924             posY = e.getPageY(),
60925             staticX = posX - me.el.getX(),
60926             staticY = posY - me.el.getY(),
60927             maskMouseDown = me.maskMouseDown,
60928             path;
60929         
60930         me.mouseMoved = me.mouseDown;
60931         staticX = max(staticX, x);
60932         staticY = max(staticY, y);
60933         staticX = min(staticX, width);
60934         staticY = min(staticY, height);
60935         if (maskMouseDown && me.mouseDown) {
60936             if (mask == 'horizontal') {
60937                 staticY = y;
60938                 maskMouseDown.y = height;
60939                 posY = me.el.getY() + bbox.height + me.insetPadding;
60940             }
60941             else if (mask == 'vertical') {
60942                 staticX = x;
60943                 maskMouseDown.x = width;
60944             }
60945             width = maskMouseDown.x - staticX;
60946             height = maskMouseDown.y - staticY;
60947             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
60948             me.maskSelection = {
60949                 x: width > 0 ? staticX : staticX + width,
60950                 y: height > 0 ? staticY : staticY + height,
60951                 width: abs(width),
60952                 height: abs(height)
60953             };
60954             me.mask.updateBox(me.maskSelection);
60955             me.mask.show();
60956             me.maskSprite.setAttributes({
60957                 hidden: true    
60958             }, true);
60959         }
60960         else {
60961             if (mask == 'horizontal') {
60962                 path = ['M', staticX, y, 'L', staticX, height];
60963             }
60964             else if (mask == 'vertical') {
60965                 path = ['M', x, staticY, 'L', width, staticY];
60966             }
60967             else {
60968                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
60969             }
60970             me.maskSprite.setAttributes({
60971                 path: path,
60972                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
60973                 'stroke-width': mask === true ? 1 : 3,
60974                 hidden: false
60975             }, true);
60976         }
60977     },
60978
60979     onMouseLeave: function(e) {
60980         var me = this;
60981         me.mouseMoved = false;
60982         me.mouseDown = false;
60983         me.maskMouseDown = false;
60984         me.mask.hide();
60985         me.maskSprite.hide(true);
60986     }
60987 });
60988     
60989 /**
60990  * @class Ext.chart.Navigation
60991  *
60992  * Handles panning and zooming capabilities.
60993  *
60994  * Used as mixin by Ext.chart.Chart.
60995  */
60996 Ext.define('Ext.chart.Navigation', {
60997
60998     constructor: function() {
60999         this.originalStore = this.store;
61000     },
61001
61002     /**
61003      * Zooms the chart to the specified selection range.
61004      * Can be used with a selection mask. For example:
61005      *
61006      *     items: {
61007      *         xtype: 'chart',
61008      *         animate: true,
61009      *         store: store1,
61010      *         mask: 'horizontal',
61011      *         listeners: {
61012      *             select: {
61013      *                 fn: function(me, selection) {
61014      *                     me.setZoom(selection);
61015      *                     me.mask.hide();
61016      *                 }
61017      *             }
61018      *         }
61019      *     }
61020      */
61021     setZoom: function(zoomConfig) {
61022         var me = this,
61023             axes = me.axes,
61024             bbox = me.chartBBox,
61025             xScale = 1 / bbox.width,
61026             yScale = 1 / bbox.height,
61027             zoomer = {
61028                 x : zoomConfig.x * xScale,
61029                 y : zoomConfig.y * yScale,
61030                 width : zoomConfig.width * xScale,
61031                 height : zoomConfig.height * yScale
61032             };
61033         axes.each(function(axis) {
61034             var ends = axis.calcEnds();
61035             if (axis.position == 'bottom' || axis.position == 'top') {
61036                 var from = (ends.to - ends.from) * zoomer.x + ends.from,
61037                     to = (ends.to - ends.from) * zoomer.width + from;
61038                 axis.minimum = from;
61039                 axis.maximum = to;
61040             } else {
61041                 var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
61042                     from = to - (ends.to - ends.from) * zoomer.height;
61043                 axis.minimum = from;
61044                 axis.maximum = to;
61045             }
61046         });
61047         me.redraw(false);
61048     },
61049
61050     /**
61051      * Restores the zoom to the original value. This can be used to reset
61052      * the previous zoom state set by `setZoom`. For example:
61053      *
61054      *     myChart.restoreZoom();
61055      */
61056     restoreZoom: function() {
61057         this.store = this.substore = this.originalStore;
61058         this.redraw(true);
61059     }
61060
61061 });
61062
61063 /**
61064  * @class Ext.chart.Shape
61065  * @ignore
61066  */
61067 Ext.define('Ext.chart.Shape', {
61068
61069     /* Begin Definitions */
61070
61071     singleton: true,
61072
61073     /* End Definitions */
61074
61075     circle: function (surface, opts) {
61076         return surface.add(Ext.apply({
61077             type: 'circle',
61078             x: opts.x,
61079             y: opts.y,
61080             stroke: null,
61081             radius: opts.radius
61082         }, opts));
61083     },
61084     line: function (surface, opts) {
61085         return surface.add(Ext.apply({
61086             type: 'rect',
61087             x: opts.x - opts.radius,
61088             y: opts.y - opts.radius,
61089             height: 2 * opts.radius,
61090             width: 2 * opts.radius / 5
61091         }, opts));
61092     },
61093     square: function (surface, opts) {
61094         return surface.add(Ext.applyIf({
61095             type: 'rect',
61096             x: opts.x - opts.radius,
61097             y: opts.y - opts.radius,
61098             height: 2 * opts.radius,
61099             width: 2 * opts.radius,
61100             radius: null
61101         }, opts));
61102     },
61103     triangle: function (surface, opts) {
61104         opts.radius *= 1.75;
61105         return surface.add(Ext.apply({
61106             type: 'path',
61107             stroke: null,
61108             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
61109         }, opts));
61110     },
61111     diamond: function (surface, opts) {
61112         var r = opts.radius;
61113         r *= 1.5;
61114         return surface.add(Ext.apply({
61115             type: 'path',
61116             stroke: null,
61117             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
61118         }, opts));
61119     },
61120     cross: function (surface, opts) {
61121         var r = opts.radius;
61122         r = r / 1.7;
61123         return surface.add(Ext.apply({
61124             type: 'path',
61125             stroke: null,
61126             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"])
61127         }, opts));
61128     },
61129     plus: function (surface, opts) {
61130         var r = opts.radius / 1.3;
61131         return surface.add(Ext.apply({
61132             type: 'path',
61133             stroke: null,
61134             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"])
61135         }, opts));
61136     },
61137     arrow: function (surface, opts) {
61138         var r = opts.radius;
61139         return surface.add(Ext.apply({
61140             type: 'path',
61141             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")
61142         }, opts));
61143     },
61144     drop: function (surface, x, y, text, size, angle) {
61145         size = size || 30;
61146         angle = angle || 0;
61147         surface.add({
61148             type: 'path',
61149             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'],
61150             fill: '#000',
61151             stroke: 'none',
61152             rotate: {
61153                 degrees: 22.5 - angle,
61154                 x: x,
61155                 y: y
61156             }
61157         });
61158         angle = (angle + 90) * Math.PI / 180;
61159         surface.add({
61160             type: 'text',
61161             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
61162             y: y + size * Math.cos(angle) + 5,
61163             text:  text,
61164             'font-size': size * 12 / 40,
61165             stroke: 'none',
61166             fill: '#fff'
61167         });
61168     }
61169 });
61170 /**
61171  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
61172  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
61173  * sprites to the canvas, initialize other graphic components, etc. One of the most used
61174  * methods for this class is the `add` method, to add Sprites to the surface.
61175  *
61176  * Most of the Surface methods are abstract and they have a concrete implementation
61177  * in VML or SVG engines.
61178  *
61179  * A Surface instance can be accessed as a property of a draw component. For example:
61180  *
61181  *     drawComponent.surface.add({
61182  *         type: 'circle',
61183  *         fill: '#ffc',
61184  *         radius: 100,
61185  *         x: 100,
61186  *         y: 100
61187  *     });
61188  *
61189  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
61190  * class documentation.
61191  *
61192  * # Listeners
61193  *
61194  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
61195  *
61196  * - mousedown
61197  * - mouseup
61198  * - mouseover
61199  * - mouseout
61200  * - mousemove
61201  * - mouseenter
61202  * - mouseleave
61203  * - click
61204  *
61205  * For example:
61206  *
61207  *     drawComponent.surface.on({
61208  *        'mousemove': function() {
61209  *             console.log('moving the mouse over the surface');
61210  *         }
61211  *     });
61212  *
61213  * # Example
61214  *
61215  *     var drawComponent = Ext.create('Ext.draw.Component', {
61216  *         width: 800,
61217  *         height: 600,
61218  *         renderTo: document.body
61219  *     }), surface = drawComponent.surface;
61220  *
61221  *     surface.add([{
61222  *         type: 'circle',
61223  *         radius: 10,
61224  *         fill: '#f00',
61225  *         x: 10,
61226  *         y: 10,
61227  *         group: 'circles'
61228  *     }, {
61229  *         type: 'circle',
61230  *         radius: 10,
61231  *         fill: '#0f0',
61232  *         x: 50,
61233  *         y: 50,
61234  *         group: 'circles'
61235  *     }, {
61236  *         type: 'circle',
61237  *         radius: 10,
61238  *         fill: '#00f',
61239  *         x: 100,
61240  *         y: 100,
61241  *         group: 'circles'
61242  *     }, {
61243  *         type: 'rect',
61244  *         width: 20,
61245  *         height: 20,
61246  *         fill: '#f00',
61247  *         x: 10,
61248  *         y: 10,
61249  *         group: 'rectangles'
61250  *     }, {
61251  *         type: 'rect',
61252  *         width: 20,
61253  *         height: 20,
61254  *         fill: '#0f0',
61255  *         x: 50,
61256  *         y: 50,
61257  *         group: 'rectangles'
61258  *     }, {
61259  *         type: 'rect',
61260  *         width: 20,
61261  *         height: 20,
61262  *         fill: '#00f',
61263  *         x: 100,
61264  *         y: 100,
61265  *         group: 'rectangles'
61266  *     }]);
61267  *
61268  *     // Get references to my groups
61269  *     circles = surface.getGroup('circles');
61270  *     rectangles = surface.getGroup('rectangles');
61271  *
61272  *     // Animate the circles down
61273  *     circles.animate({
61274  *         duration: 1000,
61275  *         to: {
61276  *             translate: {
61277  *                 y: 200
61278  *             }
61279  *         }
61280  *     });
61281  *
61282  *     // Animate the rectangles across
61283  *     rectangles.animate({
61284  *         duration: 1000,
61285  *         to: {
61286  *             translate: {
61287  *                 x: 200
61288  *             }
61289  *         }
61290  *     });
61291  */
61292 Ext.define('Ext.draw.Surface', {
61293
61294     /* Begin Definitions */
61295
61296     mixins: {
61297         observable: 'Ext.util.Observable'
61298     },
61299
61300     requires: ['Ext.draw.CompositeSprite'],
61301     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
61302
61303     separatorRe: /[, ]+/,
61304
61305     statics: {
61306         /**
61307          * Creates and returns a new concrete Surface instance appropriate for the current environment.
61308          * @param {Object} config Initial configuration for the Surface instance
61309          * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
61310          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
61311          * @return {Object} The created Surface or false.
61312          * @static
61313          */
61314         create: function(config, enginePriority) {
61315             enginePriority = enginePriority || ['Svg', 'Vml'];
61316
61317             var i = 0,
61318                 len = enginePriority.length,
61319                 surfaceClass;
61320
61321             for (; i < len; i++) {
61322                 if (Ext.supports[enginePriority[i]]) {
61323                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
61324                 }
61325             }
61326             return false;
61327         }
61328     },
61329
61330     /* End Definitions */
61331
61332     // @private
61333     availableAttrs: {
61334         blur: 0,
61335         "clip-rect": "0 0 1e9 1e9",
61336         cursor: "default",
61337         cx: 0,
61338         cy: 0,
61339         'dominant-baseline': 'auto',
61340         fill: "none",
61341         "fill-opacity": 1,
61342         font: '10px "Arial"',
61343         "font-family": '"Arial"',
61344         "font-size": "10",
61345         "font-style": "normal",
61346         "font-weight": 400,
61347         gradient: "",
61348         height: 0,
61349         hidden: false,
61350         href: "http://sencha.com/",
61351         opacity: 1,
61352         path: "M0,0",
61353         radius: 0,
61354         rx: 0,
61355         ry: 0,
61356         scale: "1 1",
61357         src: "",
61358         stroke: "#000",
61359         "stroke-dasharray": "",
61360         "stroke-linecap": "butt",
61361         "stroke-linejoin": "butt",
61362         "stroke-miterlimit": 0,
61363         "stroke-opacity": 1,
61364         "stroke-width": 1,
61365         target: "_blank",
61366         text: "",
61367         "text-anchor": "middle",
61368         title: "Ext Draw",
61369         width: 0,
61370         x: 0,
61371         y: 0,
61372         zIndex: 0
61373     },
61374
61375     /**
61376      * @cfg {Number} height
61377      * The height of this component in pixels (defaults to auto).
61378      */
61379     /**
61380      * @cfg {Number} width
61381      * The width of this component in pixels (defaults to auto).
61382      */
61383
61384     container: undefined,
61385     height: 352,
61386     width: 512,
61387     x: 0,
61388     y: 0,
61389
61390     /**
61391      * @private Flag indicating that the surface implementation requires sprites to be maintained
61392      * in order of their zIndex. Impls that don't require this can set it to false.
61393      */
61394     orderSpritesByZIndex: true,
61395
61396
61397     /**
61398      * Creates new Surface.
61399      * @param {Object} config (optional) Config object.
61400      */
61401     constructor: function(config) {
61402         var me = this;
61403         config = config || {};
61404         Ext.apply(me, config);
61405
61406         me.domRef = Ext.getDoc().dom;
61407
61408         me.customAttributes = {};
61409
61410         me.addEvents(
61411             'mousedown',
61412             'mouseup',
61413             'mouseover',
61414             'mouseout',
61415             'mousemove',
61416             'mouseenter',
61417             'mouseleave',
61418             'click'
61419         );
61420
61421         me.mixins.observable.constructor.call(me);
61422
61423         me.getId();
61424         me.initGradients();
61425         me.initItems();
61426         if (me.renderTo) {
61427             me.render(me.renderTo);
61428             delete me.renderTo;
61429         }
61430         me.initBackground(config.background);
61431     },
61432
61433     // @private called to initialize components in the surface
61434     // this is dependent on the underlying implementation.
61435     initSurface: Ext.emptyFn,
61436
61437     // @private called to setup the surface to render an item
61438     //this is dependent on the underlying implementation.
61439     renderItem: Ext.emptyFn,
61440
61441     // @private
61442     renderItems: Ext.emptyFn,
61443
61444     // @private
61445     setViewBox: function (x, y, width, height) {
61446         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
61447             this.viewBox = {x: x, y: y, width: width, height: height};
61448             this.applyViewBox();
61449         }
61450     },
61451
61452     /**
61453      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
61454      *
61455      * For example:
61456      *
61457      *     drawComponent.surface.addCls(sprite, 'x-visible');
61458      *
61459      * @param {Object} sprite The sprite to add the class to.
61460      * @param {String/String[]} className The CSS class to add, or an array of classes
61461      * @method
61462      */
61463     addCls: Ext.emptyFn,
61464
61465     /**
61466      * Removes one or more CSS classes from the element.
61467      *
61468      * For example:
61469      *
61470      *     drawComponent.surface.removeCls(sprite, 'x-visible');
61471      *
61472      * @param {Object} sprite The sprite to remove the class from.
61473      * @param {String/String[]} className The CSS class to remove, or an array of classes
61474      * @method
61475      */
61476     removeCls: Ext.emptyFn,
61477
61478     /**
61479      * Sets CSS style attributes to an element.
61480      *
61481      * For example:
61482      *
61483      *     drawComponent.surface.setStyle(sprite, {
61484      *         'cursor': 'pointer'
61485      *     });
61486      *
61487      * @param {Object} sprite The sprite to add, or an array of classes to
61488      * @param {Object} styles An Object with CSS styles.
61489      * @method
61490      */
61491     setStyle: Ext.emptyFn,
61492
61493     // @private
61494     initGradients: function() {
61495         var gradients = this.gradients;
61496         if (gradients) {
61497             Ext.each(gradients, this.addGradient, this);
61498         }
61499     },
61500
61501     // @private
61502     initItems: function() {
61503         var items = this.items;
61504         this.items = Ext.create('Ext.draw.CompositeSprite');
61505         this.groups = Ext.create('Ext.draw.CompositeSprite');
61506         if (items) {
61507             this.add(items);
61508         }
61509     },
61510
61511     // @private
61512     initBackground: function(config) {
61513         var me = this,
61514             width = me.width,
61515             height = me.height,
61516             gradientId, gradient, backgroundSprite;
61517         if (config) {
61518             if (config.gradient) {
61519                 gradient = config.gradient;
61520                 gradientId = gradient.id;
61521                 me.addGradient(gradient);
61522                 me.background = me.add({
61523                     type: 'rect',
61524                     x: 0,
61525                     y: 0,
61526                     width: width,
61527                     height: height,
61528                     fill: 'url(#' + gradientId + ')'
61529                 });
61530             } else if (config.fill) {
61531                 me.background = me.add({
61532                     type: 'rect',
61533                     x: 0,
61534                     y: 0,
61535                     width: width,
61536                     height: height,
61537                     fill: config.fill
61538                 });
61539             } else if (config.image) {
61540                 me.background = me.add({
61541                     type: 'image',
61542                     x: 0,
61543                     y: 0,
61544                     width: width,
61545                     height: height,
61546                     src: config.image
61547                 });
61548             }
61549         }
61550     },
61551
61552     /**
61553      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
61554      *
61555      * For example:
61556      *
61557      *     drawComponent.surface.setSize(500, 500);
61558      *
61559      * This method is generally called when also setting the size of the draw Component.
61560      *
61561      * @param {Number} w The new width of the canvas.
61562      * @param {Number} h The new height of the canvas.
61563      */
61564     setSize: function(w, h) {
61565         if (this.background) {
61566             this.background.setAttributes({
61567                 width: w,
61568                 height: h,
61569                 hidden: false
61570             }, true);
61571         }
61572         this.applyViewBox();
61573     },
61574
61575     // @private
61576     scrubAttrs: function(sprite) {
61577         var i,
61578             attrs = {},
61579             exclude = {},
61580             sattr = sprite.attr;
61581         for (i in sattr) {
61582             // Narrow down attributes to the main set
61583             if (this.translateAttrs.hasOwnProperty(i)) {
61584                 // Translated attr
61585                 attrs[this.translateAttrs[i]] = sattr[i];
61586                 exclude[this.translateAttrs[i]] = true;
61587             }
61588             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
61589                 // Passtrhough attr
61590                 attrs[i] = sattr[i];
61591             }
61592         }
61593         return attrs;
61594     },
61595
61596     // @private
61597     onClick: function(e) {
61598         this.processEvent('click', e);
61599     },
61600
61601     // @private
61602     onMouseUp: function(e) {
61603         this.processEvent('mouseup', e);
61604     },
61605
61606     // @private
61607     onMouseDown: function(e) {
61608         this.processEvent('mousedown', e);
61609     },
61610
61611     // @private
61612     onMouseOver: function(e) {
61613         this.processEvent('mouseover', e);
61614     },
61615
61616     // @private
61617     onMouseOut: function(e) {
61618         this.processEvent('mouseout', e);
61619     },
61620
61621     // @private
61622     onMouseMove: function(e) {
61623         this.fireEvent('mousemove', e);
61624     },
61625
61626     // @private
61627     onMouseEnter: Ext.emptyFn,
61628
61629     // @private
61630     onMouseLeave: Ext.emptyFn,
61631
61632     /**
61633      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
61634      * a gradient via this method will not take effect if the surface has already been rendered.
61635      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
61636      * than calling this method, especially if the surface is rendered immediately (e.g. due to
61637      * 'renderTo' in its config). For more information on how to create gradients in the Chart
61638      * configuration object please refer to {@link Ext.chart.Chart}.
61639      *
61640      * The gradient object to be passed into this method is composed by:
61641      *
61642      * - **id** - string - The unique name of the gradient.
61643      * - **angle** - number, optional - The angle of the gradient in degrees.
61644      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
61645      *
61646      * For example:
61647      *
61648      *    drawComponent.surface.addGradient({
61649      *        id: 'gradientId',
61650      *        angle: 45,
61651      *        stops: {
61652      *            0: {
61653      *                color: '#555'
61654      *            },
61655      *            100: {
61656      *                color: '#ddd'
61657      *            }
61658      *        }
61659      *    });
61660      *
61661      * @method
61662      */
61663     addGradient: Ext.emptyFn,
61664
61665     /**
61666      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
61667      * passed into this method.
61668      *
61669      * For example:
61670      *
61671      *     drawComponent.surface.add({
61672      *         type: 'circle',
61673      *         fill: '#ffc',
61674      *         radius: 100,
61675      *         x: 100,
61676      *         y: 100
61677      *     });
61678      *
61679      */
61680     add: function() {
61681         var args = Array.prototype.slice.call(arguments),
61682             sprite,
61683             index;
61684
61685         var hasMultipleArgs = args.length > 1;
61686         if (hasMultipleArgs || Ext.isArray(args[0])) {
61687             var items = hasMultipleArgs ? args : args[0],
61688                 results = [],
61689                 i, ln, item;
61690
61691             for (i = 0, ln = items.length; i < ln; i++) {
61692                 item = items[i];
61693                 item = this.add(item);
61694                 results.push(item);
61695             }
61696
61697             return results;
61698         }
61699         sprite = this.prepareItems(args[0], true)[0];
61700         this.insertByZIndex(sprite);
61701         this.onAdd(sprite);
61702         return sprite;
61703     },
61704
61705     /**
61706      * @private
61707      * Inserts a given sprite into the correct position in the items collection, according to
61708      * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
61709      * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
61710      * the sprites in the correct order for proper z-index stacking.
61711      * @param {Ext.draw.Sprite} sprite
61712      * @return {Number} the sprite's new index in the list
61713      */
61714     insertByZIndex: function(sprite) {
61715         var me = this,
61716             sprites = me.items.items,
61717             len = sprites.length,
61718             ceil = Math.ceil,
61719             zIndex = sprite.attr.zIndex,
61720             idx = len,
61721             high = idx - 1,
61722             low = 0,
61723             otherZIndex;
61724
61725         if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
61726             // Find the target index via a binary search for speed
61727             while (low <= high) {
61728                 idx = ceil((low + high) / 2);
61729                 otherZIndex = sprites[idx].attr.zIndex;
61730                 if (otherZIndex > zIndex) {
61731                     high = idx - 1;
61732                 }
61733                 else if (otherZIndex < zIndex) {
61734                     low = idx + 1;
61735                 }
61736                 else {
61737                     break;
61738                 }
61739             }
61740             // Step forward to the end of a sequence of the same or lower z-index
61741             while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
61742                 idx++;
61743             }
61744         }
61745
61746         me.items.insert(idx, sprite);
61747         return idx;
61748     },
61749
61750     onAdd: function(sprite) {
61751         var group = sprite.group,
61752             draggable = sprite.draggable,
61753             groups, ln, i;
61754         if (group) {
61755             groups = [].concat(group);
61756             ln = groups.length;
61757             for (i = 0; i < ln; i++) {
61758                 group = groups[i];
61759                 this.getGroup(group).add(sprite);
61760             }
61761             delete sprite.group;
61762         }
61763         if (draggable) {
61764             sprite.initDraggable();
61765         }
61766     },
61767
61768     /**
61769      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
61770      * You can also call the sprite own `remove` method.
61771      *
61772      * For example:
61773      *
61774      *     drawComponent.surface.remove(sprite);
61775      *     //or...
61776      *     sprite.remove();
61777      *
61778      * @param {Ext.draw.Sprite} sprite
61779      * @param {Boolean} destroySprite
61780      * @return {Number} the sprite's new index in the list
61781      */
61782     remove: function(sprite, destroySprite) {
61783         if (sprite) {
61784             this.items.remove(sprite);
61785             this.groups.each(function(item) {
61786                 item.remove(sprite);
61787             });
61788             sprite.onRemove();
61789             if (destroySprite === true) {
61790                 sprite.destroy();
61791             }
61792         }
61793     },
61794
61795     /**
61796      * Removes all sprites from the surface, optionally destroying the sprites in the process.
61797      *
61798      * For example:
61799      *
61800      *     drawComponent.surface.removeAll();
61801      *
61802      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
61803      * @return {Number} The sprite's new index in the list.
61804      */
61805     removeAll: function(destroySprites) {
61806         var items = this.items.items,
61807             ln = items.length,
61808             i;
61809         for (i = ln - 1; i > -1; i--) {
61810             this.remove(items[i], destroySprites);
61811         }
61812     },
61813
61814     onRemove: Ext.emptyFn,
61815
61816     onDestroy: Ext.emptyFn,
61817
61818     /**
61819      * @private Using the current viewBox property and the surface's width and height, calculate the
61820      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
61821      */
61822     applyViewBox: function() {
61823         var me = this,
61824             viewBox = me.viewBox,
61825             width = me.width,
61826             height = me.height,
61827             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
61828             relativeHeight, relativeWidth, size;
61829
61830         if (viewBox && (width || height)) {
61831             viewBoxX = viewBox.x;
61832             viewBoxY = viewBox.y;
61833             viewBoxWidth = viewBox.width;
61834             viewBoxHeight = viewBox.height;
61835             relativeHeight = height / viewBoxHeight;
61836             relativeWidth = width / viewBoxWidth;
61837
61838             if (viewBoxWidth * relativeHeight < width) {
61839                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
61840             }
61841             if (viewBoxHeight * relativeWidth < height) {
61842                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
61843             }
61844
61845             size = 1 / Math.min(viewBoxWidth, relativeHeight);
61846
61847             me.viewBoxShift = {
61848                 dx: -viewBoxX,
61849                 dy: -viewBoxY,
61850                 scale: size
61851             };
61852         }
61853     },
61854
61855     transformToViewBox: function (x, y) {
61856         if (this.viewBoxShift) {
61857             var me = this, shift = me.viewBoxShift;
61858             return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
61859         } else {
61860             return [x, y];
61861         }
61862     },
61863
61864     // @private
61865     applyTransformations: function(sprite) {
61866             sprite.bbox.transform = 0;
61867             this.transform(sprite);
61868
61869         var me = this,
61870             dirty = false,
61871             attr = sprite.attr;
61872
61873         if (attr.translation.x != null || attr.translation.y != null) {
61874             me.translate(sprite);
61875             dirty = true;
61876         }
61877         if (attr.scaling.x != null || attr.scaling.y != null) {
61878             me.scale(sprite);
61879             dirty = true;
61880         }
61881         if (attr.rotation.degrees != null) {
61882             me.rotate(sprite);
61883             dirty = true;
61884         }
61885         if (dirty) {
61886             sprite.bbox.transform = 0;
61887             this.transform(sprite);
61888             sprite.transformations = [];
61889         }
61890     },
61891
61892     // @private
61893     rotate: function (sprite) {
61894         var bbox,
61895             deg = sprite.attr.rotation.degrees,
61896             centerX = sprite.attr.rotation.x,
61897             centerY = sprite.attr.rotation.y;
61898         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61899             bbox = this.getBBox(sprite);
61900             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61901             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61902         }
61903         sprite.transformations.push({
61904             type: "rotate",
61905             degrees: deg,
61906             x: centerX,
61907             y: centerY
61908         });
61909     },
61910
61911     // @private
61912     translate: function(sprite) {
61913         var x = sprite.attr.translation.x || 0,
61914             y = sprite.attr.translation.y || 0;
61915         sprite.transformations.push({
61916             type: "translate",
61917             x: x,
61918             y: y
61919         });
61920     },
61921
61922     // @private
61923     scale: function(sprite) {
61924         var bbox,
61925             x = sprite.attr.scaling.x || 1,
61926             y = sprite.attr.scaling.y || 1,
61927             centerX = sprite.attr.scaling.centerX,
61928             centerY = sprite.attr.scaling.centerY;
61929
61930         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61931             bbox = this.getBBox(sprite);
61932             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61933             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61934         }
61935         sprite.transformations.push({
61936             type: "scale",
61937             x: x,
61938             y: y,
61939             centerX: centerX,
61940             centerY: centerY
61941         });
61942     },
61943
61944     // @private
61945     rectPath: function (x, y, w, h, r) {
61946         if (r) {
61947             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"]];
61948         }
61949         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
61950     },
61951
61952     // @private
61953     ellipsePath: function (x, y, rx, ry) {
61954         if (ry == null) {
61955             ry = rx;
61956         }
61957         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"]];
61958     },
61959
61960     // @private
61961     getPathpath: function (el) {
61962         return el.attr.path;
61963     },
61964
61965     // @private
61966     getPathcircle: function (el) {
61967         var a = el.attr;
61968         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
61969     },
61970
61971     // @private
61972     getPathellipse: function (el) {
61973         var a = el.attr;
61974         return this.ellipsePath(a.x, a.y,
61975                                 a.radiusX || (a.width / 2) || 0,
61976                                 a.radiusY || (a.height / 2) || 0);
61977     },
61978
61979     // @private
61980     getPathrect: function (el) {
61981         var a = el.attr;
61982         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
61983     },
61984
61985     // @private
61986     getPathimage: function (el) {
61987         var a = el.attr;
61988         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
61989     },
61990
61991     // @private
61992     getPathtext: function (el) {
61993         var bbox = this.getBBoxText(el);
61994         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
61995     },
61996
61997     createGroup: function(id) {
61998         var group = this.groups.get(id);
61999         if (!group) {
62000             group = Ext.create('Ext.draw.CompositeSprite', {
62001                 surface: this
62002             });
62003             group.id = id || Ext.id(null, 'ext-surface-group-');
62004             this.groups.add(group);
62005         }
62006         return group;
62007     },
62008
62009     /**
62010      * Returns a new group or an existent group associated with the current surface.
62011      * The group returned is a {@link Ext.draw.CompositeSprite} group.
62012      *
62013      * For example:
62014      *
62015      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
62016      *
62017      * @param {String} id The unique identifier of the group.
62018      * @return {Object} The {@link Ext.draw.CompositeSprite}.
62019      */
62020     getGroup: function(id) {
62021         if (typeof id == "string") {
62022             var group = this.groups.get(id);
62023             if (!group) {
62024                 group = this.createGroup(id);
62025             }
62026         } else {
62027             group = id;
62028         }
62029         return group;
62030     },
62031
62032     // @private
62033     prepareItems: function(items, applyDefaults) {
62034         items = [].concat(items);
62035         // Make sure defaults are applied and item is initialized
62036         var item, i, ln;
62037         for (i = 0, ln = items.length; i < ln; i++) {
62038             item = items[i];
62039             if (!(item instanceof Ext.draw.Sprite)) {
62040                 // Temporary, just take in configs...
62041                 item.surface = this;
62042                 items[i] = this.createItem(item);
62043             } else {
62044                 item.surface = this;
62045             }
62046         }
62047         return items;
62048     },
62049
62050     /**
62051      * Changes the text in the sprite element. The sprite must be a `text` sprite.
62052      * This method can also be called from {@link Ext.draw.Sprite}.
62053      *
62054      * For example:
62055      *
62056      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
62057      *
62058      * @param {Object} sprite The Sprite to change the text.
62059      * @param {String} text The new text to be set.
62060      * @method
62061      */
62062     setText: Ext.emptyFn,
62063
62064     //@private Creates an item and appends it to the surface. Called
62065     //as an internal method when calling `add`.
62066     createItem: Ext.emptyFn,
62067
62068     /**
62069      * Retrieves the id of this component.
62070      * Will autogenerate an id if one has not already been set.
62071      */
62072     getId: function() {
62073         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
62074     },
62075
62076     /**
62077      * Destroys the surface. This is done by removing all components from it and
62078      * also removing its reference to a DOM element.
62079      *
62080      * For example:
62081      *
62082      *      drawComponent.surface.destroy();
62083      */
62084     destroy: function() {
62085         delete this.domRef;
62086         this.removeAll();
62087     }
62088 });
62089 /**
62090  * @class Ext.layout.component.Draw
62091  * @extends Ext.layout.component.Component
62092  * @private
62093  *
62094  */
62095
62096 Ext.define('Ext.layout.component.Draw', {
62097
62098     /* Begin Definitions */
62099
62100     alias: 'layout.draw',
62101
62102     extend: 'Ext.layout.component.Auto',
62103
62104     /* End Definitions */
62105
62106     type: 'draw',
62107
62108     onLayout : function(width, height) {
62109         this.owner.surface.setSize(width, height);
62110         this.callParent(arguments);
62111     }
62112 });
62113 /**
62114  * @class Ext.draw.Component
62115  * @extends Ext.Component
62116  *
62117  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
62118  * manages and holds a `Surface` instance: an interface that has
62119  * an SVG or VML implementation depending on the browser capabilities and where
62120  * Sprites can be appended.
62121  *
62122  * One way to create a draw component is:
62123  *
62124  *     @example
62125  *     var drawComponent = Ext.create('Ext.draw.Component', {
62126  *         viewBox: false,
62127  *         items: [{
62128  *             type: 'circle',
62129  *             fill: '#79BB3F',
62130  *             radius: 100,
62131  *             x: 100,
62132  *             y: 100
62133  *         }]
62134  *     });
62135  *
62136  *     Ext.create('Ext.Window', {
62137  *         width: 215,
62138  *         height: 235,
62139  *         layout: 'fit',
62140  *         items: [drawComponent]
62141  *     }).show();
62142  *
62143  * In this case we created a draw component and added a sprite to it.
62144  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
62145  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
62146  * dimensions accordingly.
62147  *
62148  * You can also add sprites by using the surface's add method:
62149  *
62150  *     drawComponent.surface.add({
62151  *         type: 'circle',
62152  *         fill: '#79BB3F',
62153  *         radius: 100,
62154  *         x: 100,
62155  *         y: 100
62156  *     });
62157  *
62158  * For more information on Sprites, the core elements added to a draw component's surface,
62159  * refer to the Ext.draw.Sprite documentation.
62160  */
62161 Ext.define('Ext.draw.Component', {
62162
62163     /* Begin Definitions */
62164
62165     alias: 'widget.draw',
62166
62167     extend: 'Ext.Component',
62168
62169     requires: [
62170         'Ext.draw.Surface',
62171         'Ext.layout.component.Draw'
62172     ],
62173
62174     /* End Definitions */
62175
62176     /**
62177      * @cfg {String[]} enginePriority
62178      * Defines the priority order for which Surface implementation to use. The first
62179      * one supported by the current environment will be used.
62180      */
62181     enginePriority: ['Svg', 'Vml'],
62182
62183     baseCls: Ext.baseCSSPrefix + 'surface',
62184
62185     componentLayout: 'draw',
62186
62187     /**
62188      * @cfg {Boolean} viewBox
62189      * Turn on view box support which will scale and position items in the draw component to fit to the component while
62190      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
62191      */
62192     viewBox: true,
62193
62194     /**
62195      * @cfg {Boolean} autoSize
62196      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
62197      */
62198     autoSize: false,
62199
62200     /**
62201      * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
62202      * The gradients array is an array of objects with the following properties:
62203      *
62204      *  - `id` - string - The unique name of the gradient.
62205      *  - `angle` - number, optional - The angle of the gradient in degrees.
62206      *  - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
62207      *
62208      * ## Example
62209      *
62210      *     gradients: [{
62211      *         id: 'gradientId',
62212      *         angle: 45,
62213      *         stops: {
62214      *             0: {
62215      *                 color: '#555'
62216      *             },
62217      *             100: {
62218      *                 color: '#ddd'
62219      *             }
62220      *         }
62221      *     }, {
62222      *         id: 'gradientId2',
62223      *         angle: 0,
62224      *         stops: {
62225      *             0: {
62226      *                 color: '#590'
62227      *             },
62228      *             20: {
62229      *                 color: '#599'
62230      *             },
62231      *             100: {
62232      *                 color: '#ddd'
62233      *             }
62234      *         }
62235      *     }]
62236      *
62237      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
62238      *
62239      *     sprite.setAttributes({
62240      *         fill: 'url(#gradientId)'
62241      *     }, true);
62242      */
62243     initComponent: function() {
62244         this.callParent(arguments);
62245
62246         this.addEvents(
62247             'mousedown',
62248             'mouseup',
62249             'mousemove',
62250             'mouseenter',
62251             'mouseleave',
62252             'click'
62253         );
62254     },
62255
62256     /**
62257      * @private
62258      *
62259      * Create the Surface on initial render
62260      */
62261     onRender: function() {
62262         var me = this,
62263             viewBox = me.viewBox,
62264             autoSize = me.autoSize,
62265             bbox, items, width, height, x, y;
62266         me.callParent(arguments);
62267
62268         if (me.createSurface() !== false) {
62269             items = me.surface.items;
62270
62271             if (viewBox || autoSize) {
62272                 bbox = items.getBBox();
62273                 width = bbox.width;
62274                 height = bbox.height;
62275                 x = bbox.x;
62276                 y = bbox.y;
62277                 if (me.viewBox) {
62278                     me.surface.setViewBox(x, y, width, height);
62279                 }
62280                 else {
62281                     // AutoSized
62282                     me.autoSizeSurface();
62283                 }
62284             }
62285         }
62286     },
62287
62288     //@private
62289     autoSizeSurface: function() {
62290         var me = this,
62291             items = me.surface.items,
62292             bbox = items.getBBox(),
62293             width = bbox.width,
62294             height = bbox.height;
62295         items.setAttributes({
62296             translate: {
62297                 x: -bbox.x,
62298                 //Opera has a slight offset in the y axis.
62299                 y: -bbox.y + (+Ext.isOpera)
62300             }
62301         }, true);
62302         if (me.rendered) {
62303             me.setSize(width, height);
62304             me.surface.setSize(width, height);
62305         }
62306         else {
62307             me.surface.setSize(width, height);
62308         }
62309         me.el.setSize(width, height);
62310     },
62311
62312     /**
62313      * Create the Surface instance. Resolves the correct Surface implementation to
62314      * instantiate based on the 'enginePriority' config. Once the Surface instance is
62315      * created you can use the handle to that instance to add sprites. For example:
62316      *
62317      *     drawComponent.surface.add(sprite);
62318      */
62319     createSurface: function() {
62320         var surface = Ext.draw.Surface.create(Ext.apply({}, {
62321                 width: this.width,
62322                 height: this.height,
62323                 renderTo: this.el
62324             }, this.initialConfig));
62325         if (!surface) {
62326             // In case we cannot create a surface, return false so we can stop
62327             return false;
62328         }
62329         this.surface = surface;
62330
62331
62332         function refire(eventName) {
62333             return function(e) {
62334                 this.fireEvent(eventName, e);
62335             };
62336         }
62337
62338         surface.on({
62339             scope: this,
62340             mouseup: refire('mouseup'),
62341             mousedown: refire('mousedown'),
62342             mousemove: refire('mousemove'),
62343             mouseenter: refire('mouseenter'),
62344             mouseleave: refire('mouseleave'),
62345             click: refire('click')
62346         });
62347     },
62348
62349
62350     /**
62351      * @private
62352      *
62353      * Clean up the Surface instance on component destruction
62354      */
62355     onDestroy: function() {
62356         var surface = this.surface;
62357         if (surface) {
62358             surface.destroy();
62359         }
62360         this.callParent(arguments);
62361     }
62362
62363 });
62364
62365 /**
62366  * @class Ext.chart.LegendItem
62367  * @extends Ext.draw.CompositeSprite
62368  * A single item of a legend (marker plus label)
62369  */
62370 Ext.define('Ext.chart.LegendItem', {
62371
62372     /* Begin Definitions */
62373
62374     extend: 'Ext.draw.CompositeSprite',
62375
62376     requires: ['Ext.chart.Shape'],
62377
62378     /* End Definitions */
62379
62380     // Position of the item, relative to the upper-left corner of the legend box
62381     x: 0,
62382     y: 0,
62383     zIndex: 500,
62384
62385     constructor: function(config) {
62386         this.callParent(arguments);
62387         this.createLegend(config);
62388     },
62389
62390     /**
62391      * Creates all the individual sprites for this legend item
62392      */
62393     createLegend: function(config) {
62394         var me = this,
62395             index = config.yFieldIndex,
62396             series = me.series,
62397             seriesType = series.type,
62398             idx = me.yFieldIndex,
62399             legend = me.legend,
62400             surface = me.surface,
62401             refX = legend.x + me.x,
62402             refY = legend.y + me.y,
62403             bbox, z = me.zIndex,
62404             markerConfig, label, mask,
62405             radius, toggle = false,
62406             seriesStyle = Ext.apply(series.seriesStyle, series.style);
62407
62408         function getSeriesProp(name) {
62409             var val = series[name];
62410             return (Ext.isArray(val) ? val[idx] : val);
62411         }
62412         
62413         label = me.add('label', surface.add({
62414             type: 'text',
62415             x: 20,
62416             y: 0,
62417             zIndex: z || 0,
62418             font: legend.labelFont,
62419             text: getSeriesProp('title') || getSeriesProp('yField')
62420         }));
62421
62422         // Line series - display as short line with optional marker in the middle
62423         if (seriesType === 'line' || seriesType === 'scatter') {
62424             if(seriesType === 'line') {
62425                 me.add('line', surface.add({
62426                     type: 'path',
62427                     path: 'M0.5,0.5L16.5,0.5',
62428                     zIndex: z,
62429                     "stroke-width": series.lineWidth,
62430                     "stroke-linejoin": "round",
62431                     "stroke-dasharray": series.dash,
62432                     stroke: seriesStyle.stroke || '#000',
62433                     style: {
62434                         cursor: 'pointer'
62435                     }
62436                 }));
62437             }
62438             if (series.showMarkers || seriesType === 'scatter') {
62439                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
62440                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
62441                     fill: markerConfig.fill,
62442                     x: 8.5,
62443                     y: 0.5,
62444                     zIndex: z,
62445                     radius: markerConfig.radius || markerConfig.size,
62446                     style: {
62447                         cursor: 'pointer'
62448                     }
62449                 }));
62450             }
62451         }
62452         // All other series types - display as filled box
62453         else {
62454             me.add('box', surface.add({
62455                 type: 'rect',
62456                 zIndex: z,
62457                 x: 0,
62458                 y: 0,
62459                 width: 12,
62460                 height: 12,
62461                 fill: series.getLegendColor(index),
62462                 style: {
62463                     cursor: 'pointer'
62464                 }
62465             }));
62466         }
62467         
62468         me.setAttributes({
62469             hidden: false
62470         }, true);
62471         
62472         bbox = me.getBBox();
62473         
62474         mask = me.add('mask', surface.add({
62475             type: 'rect',
62476             x: bbox.x,
62477             y: bbox.y,
62478             width: bbox.width || 20,
62479             height: bbox.height || 20,
62480             zIndex: (z || 0) + 1000,
62481             fill: '#f00',
62482             opacity: 0,
62483             style: {
62484                 'cursor': 'pointer'
62485             }
62486         }));
62487
62488         //add toggle listener
62489         me.on('mouseover', function() {
62490             label.setStyle({
62491                 'font-weight': 'bold'
62492             });
62493             mask.setStyle({
62494                 'cursor': 'pointer'
62495             });
62496             series._index = index;
62497             series.highlightItem();
62498         }, me);
62499
62500         me.on('mouseout', function() {
62501             label.setStyle({
62502                 'font-weight': 'normal'
62503             });
62504             series._index = index;
62505             series.unHighlightItem();
62506         }, me);
62507         
62508         if (!series.visibleInLegend(index)) {
62509             toggle = true;
62510             label.setAttributes({
62511                opacity: 0.5
62512             }, true);
62513         }
62514
62515         me.on('mousedown', function() {
62516             if (!toggle) {
62517                 series.hideAll();
62518                 label.setAttributes({
62519                     opacity: 0.5
62520                 }, true);
62521             } else {
62522                 series.showAll();
62523                 label.setAttributes({
62524                     opacity: 1
62525                 }, true);
62526             }
62527             toggle = !toggle;
62528         }, me);
62529         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
62530     },
62531
62532     /**
62533      * Update the positions of all this item's sprites to match the root position
62534      * of the legend box.
62535      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
62536      *                 as the reference point for the relative positioning. Defaults to the Legend.
62537      */
62538     updatePosition: function(relativeTo) {
62539         var me = this,
62540             items = me.items,
62541             ln = items.length,
62542             i = 0,
62543             item;
62544         if (!relativeTo) {
62545             relativeTo = me.legend;
62546         }
62547         for (; i < ln; i++) {
62548             item = items[i];
62549             switch (item.type) {
62550                 case 'text':
62551                     item.setAttributes({
62552                         x: 20 + relativeTo.x + me.x,
62553                         y: relativeTo.y + me.y
62554                     }, true);
62555                     break;
62556                 case 'rect':
62557                     item.setAttributes({
62558                         translate: {
62559                             x: relativeTo.x + me.x,
62560                             y: relativeTo.y + me.y - 6
62561                         }
62562                     }, true);
62563                     break;
62564                 default:
62565                     item.setAttributes({
62566                         translate: {
62567                             x: relativeTo.x + me.x,
62568                             y: relativeTo.y + me.y
62569                         }
62570                     }, true);
62571             }
62572         }
62573     }
62574 });
62575
62576 /**
62577  * @class Ext.chart.Legend
62578  *
62579  * Defines a legend for a chart's series.
62580  * The 'chart' member must be set prior to rendering.
62581  * The legend class displays a list of legend items each of them related with a
62582  * series being rendered. In order to render the legend item of the proper series
62583  * the series configuration object must have `showInSeries` set to true.
62584  *
62585  * The legend configuration object accepts a `position` as parameter.
62586  * The `position` parameter can be `left`, `right`
62587  * `top` or `bottom`. For example:
62588  *
62589  *     legend: {
62590  *         position: 'right'
62591  *     },
62592  *
62593  * ## Example
62594  *
62595  *     @example
62596  *     var store = Ext.create('Ext.data.JsonStore', {
62597  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
62598  *         data: [
62599  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
62600  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
62601  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
62602  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
62603  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
62604  *         ]
62605  *     });
62606  *
62607  *     Ext.create('Ext.chart.Chart', {
62608  *         renderTo: Ext.getBody(),
62609  *         width: 500,
62610  *         height: 300,
62611  *         animate: true,
62612  *         store: store,
62613  *         shadow: true,
62614  *         theme: 'Category1',
62615  *         legend: {
62616  *             position: 'top'
62617  *         },
62618  *         axes: [
62619  *             {
62620  *                 type: 'Numeric',
62621  *                 grid: true,
62622  *                 position: 'left',
62623  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
62624  *                 title: 'Sample Values',
62625  *                 grid: {
62626  *                     odd: {
62627  *                         opacity: 1,
62628  *                         fill: '#ddd',
62629  *                         stroke: '#bbb',
62630  *                         'stroke-width': 1
62631  *                     }
62632  *                 },
62633  *                 minimum: 0,
62634  *                 adjustMinimumByMajorUnit: 0
62635  *             },
62636  *             {
62637  *                 type: 'Category',
62638  *                 position: 'bottom',
62639  *                 fields: ['name'],
62640  *                 title: 'Sample Metrics',
62641  *                 grid: true,
62642  *                 label: {
62643  *                     rotate: {
62644  *                         degrees: 315
62645  *                     }
62646  *                 }
62647  *             }
62648  *         ],
62649  *         series: [{
62650  *             type: 'area',
62651  *             highlight: false,
62652  *             axis: 'left',
62653  *             xField: 'name',
62654  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
62655  *             style: {
62656  *                 opacity: 0.93
62657  *             }
62658  *         }]
62659  *     });
62660  */
62661 Ext.define('Ext.chart.Legend', {
62662
62663     /* Begin Definitions */
62664
62665     requires: ['Ext.chart.LegendItem'],
62666
62667     /* End Definitions */
62668
62669     /**
62670      * @cfg {Boolean} visible
62671      * Whether or not the legend should be displayed.
62672      */
62673     visible: true,
62674
62675     /**
62676      * @cfg {String} position
62677      * The position of the legend in relation to the chart. One of: "top",
62678      * "bottom", "left", "right", or "float". If set to "float", then the legend
62679      * box will be positioned at the point denoted by the x and y parameters.
62680      */
62681     position: 'bottom',
62682
62683     /**
62684      * @cfg {Number} x
62685      * X-position of the legend box. Used directly if position is set to "float", otherwise
62686      * it will be calculated dynamically.
62687      */
62688     x: 0,
62689
62690     /**
62691      * @cfg {Number} y
62692      * Y-position of the legend box. Used directly if position is set to "float", otherwise
62693      * it will be calculated dynamically.
62694      */
62695     y: 0,
62696
62697     /**
62698      * @cfg {String} labelFont
62699      * Font to be used for the legend labels, eg '12px Helvetica'
62700      */
62701     labelFont: '12px Helvetica, sans-serif',
62702
62703     /**
62704      * @cfg {String} boxStroke
62705      * Style of the stroke for the legend box
62706      */
62707     boxStroke: '#000',
62708
62709     /**
62710      * @cfg {String} boxStrokeWidth
62711      * Width of the stroke for the legend box
62712      */
62713     boxStrokeWidth: 1,
62714
62715     /**
62716      * @cfg {String} boxFill
62717      * Fill style for the legend box
62718      */
62719     boxFill: '#FFF',
62720
62721     /**
62722      * @cfg {Number} itemSpacing
62723      * Amount of space between legend items
62724      */
62725     itemSpacing: 10,
62726
62727     /**
62728      * @cfg {Number} padding
62729      * Amount of padding between the legend box's border and its items
62730      */
62731     padding: 5,
62732
62733     // @private
62734     width: 0,
62735     // @private
62736     height: 0,
62737
62738     /**
62739      * @cfg {Number} boxZIndex
62740      * Sets the z-index for the legend. Defaults to 100.
62741      */
62742     boxZIndex: 100,
62743
62744     /**
62745      * Creates new Legend.
62746      * @param {Object} config  (optional) Config object.
62747      */
62748     constructor: function(config) {
62749         var me = this;
62750         if (config) {
62751             Ext.apply(me, config);
62752         }
62753         me.items = [];
62754         /**
62755          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
62756          * @type {Boolean}
62757          */
62758         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
62759
62760         // cache these here since they may get modified later on
62761         me.origX = me.x;
62762         me.origY = me.y;
62763     },
62764
62765     /**
62766      * @private Create all the sprites for the legend
62767      */
62768     create: function() {
62769         var me = this;
62770         me.createBox();
62771         me.createItems();
62772         if (!me.created && me.isDisplayed()) {
62773             me.created = true;
62774
62775             // Listen for changes to series titles to trigger regeneration of the legend
62776             me.chart.series.each(function(series) {
62777                 series.on('titlechange', function() {
62778                     me.create();
62779                     me.updatePosition();
62780                 });
62781             });
62782         }
62783     },
62784
62785     /**
62786      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
62787      * and also the 'showInLegend' config for each of the series.
62788      */
62789     isDisplayed: function() {
62790         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
62791     },
62792
62793     /**
62794      * @private Create the series markers and labels
62795      */
62796     createItems: function() {
62797         var me = this,
62798             chart = me.chart,
62799             surface = chart.surface,
62800             items = me.items,
62801             padding = me.padding,
62802             itemSpacing = me.itemSpacing,
62803             spacingOffset = 2,
62804             maxWidth = 0,
62805             maxHeight = 0,
62806             totalWidth = 0,
62807             totalHeight = 0,
62808             vertical = me.isVertical,
62809             math = Math,
62810             mfloor = math.floor,
62811             mmax = math.max,
62812             index = 0,
62813             i = 0,
62814             len = items ? items.length : 0,
62815             x, y, spacing, item, bbox, height, width;
62816
62817         //remove all legend items
62818         if (len) {
62819             for (; i < len; i++) {
62820                 items[i].destroy();
62821             }
62822         }
62823         //empty array
62824         items.length = [];
62825         // Create all the item labels, collecting their dimensions and positioning each one
62826         // properly in relation to the previous item
62827         chart.series.each(function(series, i) {
62828             if (series.showInLegend) {
62829                 Ext.each([].concat(series.yField), function(field, j) {
62830                     item = Ext.create('Ext.chart.LegendItem', {
62831                         legend: this,
62832                         series: series,
62833                         surface: chart.surface,
62834                         yFieldIndex: j
62835                     });
62836                     bbox = item.getBBox();
62837
62838                     //always measure from x=0, since not all markers go all the way to the left
62839                     width = bbox.width;
62840                     height = bbox.height;
62841
62842                     if (i + j === 0) {
62843                         spacing = vertical ? padding + height / 2 : padding;
62844                     }
62845                     else {
62846                         spacing = itemSpacing / (vertical ? 2 : 1);
62847                     }
62848                     // Set the item's position relative to the legend box
62849                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
62850                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
62851
62852                     // Collect cumulative dimensions
62853                     totalWidth += width + spacing;
62854                     totalHeight += height + spacing;
62855                     maxWidth = mmax(maxWidth, width);
62856                     maxHeight = mmax(maxHeight, height);
62857
62858                     items.push(item);
62859                 }, this);
62860             }
62861         }, me);
62862
62863         // Store the collected dimensions for later
62864         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
62865         if (vertical && items.length === 1) {
62866             spacingOffset = 1;
62867         }
62868         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
62869         me.itemHeight = maxHeight;
62870     },
62871
62872     /**
62873      * @private Get the bounds for the legend's outer box
62874      */
62875     getBBox: function() {
62876         var me = this;
62877         return {
62878             x: Math.round(me.x) - me.boxStrokeWidth / 2,
62879             y: Math.round(me.y) - me.boxStrokeWidth / 2,
62880             width: me.width,
62881             height: me.height
62882         };
62883     },
62884
62885     /**
62886      * @private Create the box around the legend items
62887      */
62888     createBox: function() {
62889         var me = this,
62890             box;
62891
62892         if (me.boxSprite) {
62893             me.boxSprite.destroy();
62894         }
62895         
62896         box = me.boxSprite = me.chart.surface.add(Ext.apply({
62897             type: 'rect',
62898             stroke: me.boxStroke,
62899             "stroke-width": me.boxStrokeWidth,
62900             fill: me.boxFill,
62901             zIndex: me.boxZIndex
62902         }, me.getBBox()));
62903
62904         box.redraw();
62905     },
62906
62907     /**
62908      * @private Update the position of all the legend's sprites to match its current x/y values
62909      */
62910     updatePosition: function() {
62911         var me = this,
62912             x, y,
62913             legendWidth = me.width,
62914             legendHeight = me.height,
62915             padding = me.padding,
62916             chart = me.chart,
62917             chartBBox = chart.chartBBox,
62918             insets = chart.insetPadding,
62919             chartWidth = chartBBox.width - (insets * 2),
62920             chartHeight = chartBBox.height - (insets * 2),
62921             chartX = chartBBox.x + insets,
62922             chartY = chartBBox.y + insets,
62923             surface = chart.surface,
62924             mfloor = Math.floor;
62925
62926         if (me.isDisplayed()) {
62927             // Find the position based on the dimensions
62928             switch(me.position) {
62929                 case "left":
62930                     x = insets;
62931                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62932                     break;
62933                 case "right":
62934                     x = mfloor(surface.width - legendWidth) - insets;
62935                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62936                     break;
62937                 case "top":
62938                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62939                     y = insets;
62940                     break;
62941                 case "bottom":
62942                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62943                     y = mfloor(surface.height - legendHeight) - insets;
62944                     break;
62945                 default:
62946                     x = mfloor(me.origX) + insets;
62947                     y = mfloor(me.origY) + insets;
62948             }
62949             me.x = x;
62950             me.y = y;
62951
62952             // Update the position of each item
62953             Ext.each(me.items, function(item) {
62954                 item.updatePosition();
62955             });
62956             // Update the position of the outer box
62957             me.boxSprite.setAttributes(me.getBBox(), true);
62958         }
62959     }
62960 });
62961
62962 /**
62963  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
62964  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
62965  * updates its display whenever data in the Store changes. In addition, the look and feel
62966  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
62967  * 
62968  * ## Creating a Simple Chart
62969  * 
62970  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
62971  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
62972  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
62973  * 
62974  * ### 1. Creating a Store
62975  * 
62976  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
62977  * data that will be displayed in the Chart. For example the data for a chart that displays
62978  * a weather forecast could be represented as a series of "WeatherPoint" data points with
62979  * two fields - "temperature", and "date":
62980  * 
62981  *     Ext.define('WeatherPoint', {
62982  *         extend: 'Ext.data.Model',
62983  *         fields: ['temperature', 'date']
62984  *     });
62985  * 
62986  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
62987  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
62988  * 
62989  *     var store = Ext.create('Ext.data.Store', {
62990  *         model: 'WeatherPoint',
62991  *         data: [
62992  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
62993  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
62994  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
62995  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
62996  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
62997  *         ]
62998  *     });
62999  *    
63000  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
63001  * 
63002  * ### 2. Creating the Chart object
63003  * 
63004  * Now that a Store has been created it can be used in a Chart:
63005  * 
63006  *     Ext.create('Ext.chart.Chart', {
63007  *        renderTo: Ext.getBody(),
63008  *        width: 400,
63009  *        height: 300,
63010  *        store: store
63011  *     });
63012  *    
63013  * That's all it takes to create a Chart instance that is backed by a Store.
63014  * However, if the above code is run in a browser, a blank screen will be displayed.
63015  * This is because the two pieces that are responsible for the visual display,
63016  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
63017  * 
63018  * ### 3. Configuring the Axes
63019  * 
63020  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
63021  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
63022  * 
63023  *     Ext.create('Ext.chart.Chart', {
63024  *         ...
63025  *         axes: [
63026  *             {
63027  *                 title: 'Temperature',
63028  *                 type: 'Numeric',
63029  *                 position: 'left',
63030  *                 fields: ['temperature'],
63031  *                 minimum: 0,
63032  *                 maximum: 100
63033  *             },
63034  *             {
63035  *                 title: 'Time',
63036  *                 type: 'Time',
63037  *                 position: 'bottom',
63038  *                 fields: ['date'],
63039  *                 dateFormat: 'ga'
63040  *             }
63041  *         ]
63042  *     });
63043  *    
63044  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
63045  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
63046  * defined above. The minimum value for this axis is "0", and the maximum is "100".
63047  * 
63048  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
63049  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
63050  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
63051  * configuration tells the Time Axis how to format it's labels.
63052  * 
63053  * Here's what the Chart looks like now that it has its Axes configured:
63054  * 
63055  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
63056  * 
63057  * ### 4. Configuring the Series
63058  * 
63059  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
63060  * Series are responsible for the visual representation of the data points contained in the Store.
63061  * This example only has one Series:
63062  * 
63063  *     Ext.create('Ext.chart.Chart', {
63064  *         ...
63065  *         axes: [
63066  *             ...
63067  *         ],
63068  *         series: [
63069  *             {
63070  *                 type: 'line',
63071  *                 xField: 'date',
63072  *                 yField: 'temperature'
63073  *             }
63074  *         ]
63075  *     });
63076  *     
63077  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
63078  * from the "WeatherPoint" Models in the Store to plot its data points:
63079  * 
63080  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
63081  * 
63082  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
63083  * 
63084  * ## Themes
63085  * 
63086  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
63087  * 
63088  *     Ext.create('Ext.chart.Chart', {
63089  *         ...
63090  *         theme: 'Green',
63091  *         ...
63092  *     });
63093  * 
63094  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
63095  * 
63096  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
63097  * 
63098  */
63099 Ext.define('Ext.chart.Chart', {
63100
63101     /* Begin Definitions */
63102
63103     alias: 'widget.chart',
63104
63105     extend: 'Ext.draw.Component',
63106     
63107     mixins: {
63108         themeManager: 'Ext.chart.theme.Theme',
63109         mask: 'Ext.chart.Mask',
63110         navigation: 'Ext.chart.Navigation'
63111     },
63112
63113     requires: [
63114         'Ext.util.MixedCollection',
63115         'Ext.data.StoreManager',
63116         'Ext.chart.Legend',
63117         'Ext.util.DelayedTask'
63118     ],
63119
63120     /* End Definitions */
63121
63122     // @private
63123     viewBox: false,
63124
63125     /**
63126      * @cfg {String} theme
63127      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
63128      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
63129      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
63130      * is 'Base'.
63131      */
63132
63133     /**
63134      * @cfg {Boolean/Object} animate
63135      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
63136      * object to be used for default chart animations. Defaults to false.
63137      */
63138     animate: false,
63139
63140     /**
63141      * @cfg {Boolean/Object} legend
63142      * True for the default legend display or a legend config object. Defaults to false.
63143      */
63144     legend: false,
63145
63146     /**
63147      * @cfg {Number} insetPadding
63148      * The amount of inset padding in pixels for the chart. Defaults to 10.
63149      */
63150     insetPadding: 10,
63151
63152     /**
63153      * @cfg {String[]} enginePriority
63154      * Defines the priority order for which Surface implementation to use. The first one supported by the current
63155      * environment will be used. Defaults to `['Svg', 'Vml']`.
63156      */
63157     enginePriority: ['Svg', 'Vml'],
63158
63159     /**
63160      * @cfg {Object/Boolean} background
63161      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
63162      * background. For example, if `background` were to be a color we could set the object as
63163      *
63164      *     background: {
63165      *         //color string
63166      *         fill: '#ccc'
63167      *     }
63168      *
63169      * You can specify an image by using:
63170      *
63171      *     background: {
63172      *         image: 'http://path.to.image/'
63173      *     }
63174      *
63175      * Also you can specify a gradient by using the gradient object syntax:
63176      *
63177      *     background: {
63178      *         gradient: {
63179      *             id: 'gradientId',
63180      *             angle: 45,
63181      *             stops: {
63182      *                 0: {
63183      *                     color: '#555'
63184      *                 }
63185      *                 100: {
63186      *                     color: '#ddd'
63187      *                 }
63188      *             }
63189      *         }
63190      *     }
63191      */
63192     background: false,
63193
63194     /**
63195      * @cfg {Object[]} gradients
63196      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
63197      * array of objects with the following properties:
63198      *
63199      * - **id** - string - The unique name of the gradient.
63200      * - **angle** - number, optional - The angle of the gradient in degrees.
63201      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
63202      *
63203      * For example:
63204      *
63205      *     gradients: [{
63206      *         id: 'gradientId',
63207      *         angle: 45,
63208      *         stops: {
63209      *             0: {
63210      *                 color: '#555'
63211      *             },
63212      *             100: {
63213      *                 color: '#ddd'
63214      *             }
63215      *         }
63216      *     }, {
63217      *         id: 'gradientId2',
63218      *         angle: 0,
63219      *         stops: {
63220      *             0: {
63221      *                 color: '#590'
63222      *             },
63223      *             20: {
63224      *                 color: '#599'
63225      *             },
63226      *             100: {
63227      *                 color: '#ddd'
63228      *             }
63229      *         }
63230      *     }]
63231      *
63232      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
63233      *
63234      *     sprite.setAttributes({
63235      *         fill: 'url(#gradientId)'
63236      *     }, true);
63237      */
63238
63239     /**
63240      * @cfg {Ext.data.Store} store
63241      * The store that supplies data to this chart.
63242      */
63243
63244     /**
63245      * @cfg {Ext.chart.series.Series[]} series
63246      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
63247      * 
63248      *     series: [{
63249      *         type: 'column',
63250      *         axis: 'left',
63251      *         listeners: {
63252      *             'afterrender': function() {
63253      *                 console('afterrender');
63254      *             }
63255      *         },
63256      *         xField: 'category',
63257      *         yField: 'data1'
63258      *     }]
63259      */
63260
63261     /**
63262      * @cfg {Ext.chart.axis.Axis[]} axes
63263      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
63264      * 
63265      *     axes: [{
63266      *         type: 'Numeric',
63267      *         position: 'left',
63268      *         fields: ['data1'],
63269      *         title: 'Number of Hits',
63270      *         minimum: 0,
63271      *         //one minor tick between two major ticks
63272      *         minorTickSteps: 1
63273      *     }, {
63274      *         type: 'Category',
63275      *         position: 'bottom',
63276      *         fields: ['name'],
63277      *         title: 'Month of the Year'
63278      *     }]
63279      */
63280
63281     constructor: function(config) {
63282         var me = this,
63283             defaultAnim;
63284             
63285         config = Ext.apply({}, config);
63286         me.initTheme(config.theme || me.theme);
63287         if (me.gradients) {
63288             Ext.apply(config, { gradients: me.gradients });
63289         }
63290         if (me.background) {
63291             Ext.apply(config, { background: me.background });
63292         }
63293         if (config.animate) {
63294             defaultAnim = {
63295                 easing: 'ease',
63296                 duration: 500
63297             };
63298             if (Ext.isObject(config.animate)) {
63299                 config.animate = Ext.applyIf(config.animate, defaultAnim);
63300             }
63301             else {
63302                 config.animate = defaultAnim;
63303             }
63304         }
63305         me.mixins.mask.constructor.call(me, config);
63306         me.mixins.navigation.constructor.call(me, config);
63307         me.callParent([config]);
63308     },
63309     
63310     getChartStore: function(){
63311         return this.substore || this.store;    
63312     },
63313
63314     initComponent: function() {
63315         var me = this,
63316             axes,
63317             series;
63318         me.callParent();
63319         me.addEvents(
63320             'itemmousedown',
63321             'itemmouseup',
63322             'itemmouseover',
63323             'itemmouseout',
63324             'itemclick',
63325             'itemdoubleclick',
63326             'itemdragstart',
63327             'itemdrag',
63328             'itemdragend',
63329             /**
63330              * @event beforerefresh
63331              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
63332              * {@link #refresh} action will be cancelled.
63333              * @param {Ext.chart.Chart} this
63334              */
63335             'beforerefresh',
63336             /**
63337              * @event refresh
63338              * Fires after the chart data has been refreshed.
63339              * @param {Ext.chart.Chart} this
63340              */
63341             'refresh'
63342         );
63343         Ext.applyIf(me, {
63344             zoom: {
63345                 width: 1,
63346                 height: 1,
63347                 x: 0,
63348                 y: 0
63349             }
63350         });
63351         me.maxGutter = [0, 0];
63352         me.store = Ext.data.StoreManager.lookup(me.store);
63353         axes = me.axes;
63354         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
63355         if (axes) {
63356             me.axes.addAll(axes);
63357         }
63358         series = me.series;
63359         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
63360         if (series) {
63361             me.series.addAll(series);
63362         }
63363         if (me.legend !== false) {
63364             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
63365         }
63366
63367         me.on({
63368             mousemove: me.onMouseMove,
63369             mouseleave: me.onMouseLeave,
63370             mousedown: me.onMouseDown,
63371             mouseup: me.onMouseUp,
63372             scope: me
63373         });
63374     },
63375
63376     // @private overrides the component method to set the correct dimensions to the chart.
63377     afterComponentLayout: function(width, height) {
63378         var me = this;
63379         if (Ext.isNumber(width) && Ext.isNumber(height)) {
63380             me.curWidth = width;
63381             me.curHeight = height;
63382             me.redraw(true);
63383         }
63384         this.callParent(arguments);
63385     },
63386
63387     /**
63388      * Redraws the chart. If animations are set this will animate the chart too. 
63389      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
63390      */
63391     redraw: function(resize) {
63392         var me = this,
63393             chartBBox = me.chartBBox = {
63394                 x: 0,
63395                 y: 0,
63396                 height: me.curHeight,
63397                 width: me.curWidth
63398             },
63399             legend = me.legend;
63400         me.surface.setSize(chartBBox.width, chartBBox.height);
63401         // Instantiate Series and Axes
63402         me.series.each(me.initializeSeries, me);
63403         me.axes.each(me.initializeAxis, me);
63404         //process all views (aggregated data etc) on stores
63405         //before rendering.
63406         me.axes.each(function(axis) {
63407             axis.processView();
63408         });
63409         me.axes.each(function(axis) {
63410             axis.drawAxis(true);
63411         });
63412
63413         // Create legend if not already created
63414         if (legend !== false) {
63415             legend.create();
63416         }
63417
63418         // Place axes properly, including influence from each other
63419         me.alignAxes();
63420
63421         // Reposition legend based on new axis alignment
63422         if (me.legend !== false) {
63423             legend.updatePosition();
63424         }
63425
63426         // Find the max gutter
63427         me.getMaxGutter();
63428
63429         // Draw axes and series
63430         me.resizing = !!resize;
63431
63432         me.axes.each(me.drawAxis, me);
63433         me.series.each(me.drawCharts, me);
63434         me.resizing = false;
63435     },
63436
63437     // @private set the store after rendering the chart.
63438     afterRender: function() {
63439         var ref,
63440             me = this;
63441         this.callParent();
63442
63443         if (me.categoryNames) {
63444             me.setCategoryNames(me.categoryNames);
63445         }
63446
63447         if (me.tipRenderer) {
63448             ref = me.getFunctionRef(me.tipRenderer);
63449             me.setTipRenderer(ref.fn, ref.scope);
63450         }
63451         me.bindStore(me.store, true);
63452         me.refresh();
63453     },
63454
63455     // @private get x and y position of the mouse cursor.
63456     getEventXY: function(e) {
63457         var me = this,
63458             box = this.surface.getRegion(),
63459             pageXY = e.getXY(),
63460             x = pageXY[0] - box.left,
63461             y = pageXY[1] - box.top;
63462         return [x, y];
63463     },
63464
63465     // @private wrap the mouse down position to delegate the event to the series.
63466     onClick: function(e) {
63467         var me = this,
63468             position = me.getEventXY(e),
63469             item;
63470
63471         // Ask each series if it has an item corresponding to (not necessarily exactly
63472         // on top of) the current mouse coords. Fire itemclick event.
63473         me.series.each(function(series) {
63474             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63475                 if (series.getItemForPoint) {
63476                     item = series.getItemForPoint(position[0], position[1]);
63477                     if (item) {
63478                         series.fireEvent('itemclick', item);
63479                     }
63480                 }
63481             }
63482         }, me);
63483     },
63484
63485     // @private wrap the mouse down position to delegate the event to the series.
63486     onMouseDown: function(e) {
63487         var me = this,
63488             position = me.getEventXY(e),
63489             item;
63490
63491         if (me.mask) {
63492             me.mixins.mask.onMouseDown.call(me, e);
63493         }
63494         // Ask each series if it has an item corresponding to (not necessarily exactly
63495         // on top of) the current mouse coords. Fire mousedown event.
63496         me.series.each(function(series) {
63497             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63498                 if (series.getItemForPoint) {
63499                     item = series.getItemForPoint(position[0], position[1]);
63500                     if (item) {
63501                         series.fireEvent('itemmousedown', item);
63502                     }
63503                 }
63504             }
63505         }, me);
63506     },
63507
63508     // @private wrap the mouse up event to delegate it to the series.
63509     onMouseUp: function(e) {
63510         var me = this,
63511             position = me.getEventXY(e),
63512             item;
63513
63514         if (me.mask) {
63515             me.mixins.mask.onMouseUp.call(me, e);
63516         }
63517         // Ask each series if it has an item corresponding to (not necessarily exactly
63518         // on top of) the current mouse coords. Fire mousedown event.
63519         me.series.each(function(series) {
63520             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63521                 if (series.getItemForPoint) {
63522                     item = series.getItemForPoint(position[0], position[1]);
63523                     if (item) {
63524                         series.fireEvent('itemmouseup', item);
63525                     }
63526                 }
63527             }
63528         }, me);
63529     },
63530
63531     // @private wrap the mouse move event so it can be delegated to the series.
63532     onMouseMove: function(e) {
63533         var me = this,
63534             position = me.getEventXY(e),
63535             item, last, storeItem, storeField;
63536
63537         if (me.mask) {
63538             me.mixins.mask.onMouseMove.call(me, e);
63539         }
63540         // Ask each series if it has an item corresponding to (not necessarily exactly
63541         // on top of) the current mouse coords. Fire itemmouseover/out events.
63542         me.series.each(function(series) {
63543             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
63544                 if (series.getItemForPoint) {
63545                     item = series.getItemForPoint(position[0], position[1]);
63546                     last = series._lastItemForPoint;
63547                     storeItem = series._lastStoreItem;
63548                     storeField = series._lastStoreField;
63549
63550
63551                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
63552                         if (last) {
63553                             series.fireEvent('itemmouseout', last);
63554                             delete series._lastItemForPoint;
63555                             delete series._lastStoreField;
63556                             delete series._lastStoreItem;
63557                         }
63558                         if (item) {
63559                             series.fireEvent('itemmouseover', item);
63560                             series._lastItemForPoint = item;
63561                             series._lastStoreItem = item.storeItem;
63562                             series._lastStoreField = item.storeField;
63563                         }
63564                     }
63565                 }
63566             } else {
63567                 last = series._lastItemForPoint;
63568                 if (last) {
63569                     series.fireEvent('itemmouseout', last);
63570                     delete series._lastItemForPoint;
63571                     delete series._lastStoreField;
63572                     delete series._lastStoreItem;
63573                 }
63574             }
63575         }, me);
63576     },
63577
63578     // @private handle mouse leave event.
63579     onMouseLeave: function(e) {
63580         var me = this;
63581         if (me.mask) {
63582             me.mixins.mask.onMouseLeave.call(me, e);
63583         }
63584         me.series.each(function(series) {
63585             delete series._lastItemForPoint;
63586         });
63587     },
63588
63589     // @private buffered refresh for when we update the store
63590     delayRefresh: function() {
63591         var me = this;
63592         if (!me.refreshTask) {
63593             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
63594         }
63595         me.refreshTask.delay(me.refreshBuffer);
63596     },
63597
63598     // @private
63599     refresh: function() {
63600         var me = this;
63601         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
63602             if (me.fireEvent('beforerefresh', me) !== false) {
63603                 me.redraw();
63604                 me.fireEvent('refresh', me);
63605             }
63606         }
63607     },
63608
63609     /**
63610      * Changes the data store bound to this chart and refreshes it.
63611      * @param {Ext.data.Store} store The store to bind to this chart
63612      */
63613     bindStore: function(store, initial) {
63614         var me = this;
63615         if (!initial && me.store) {
63616             if (store !== me.store && me.store.autoDestroy) {
63617                 me.store.destroyStore();
63618             }
63619             else {
63620                 me.store.un('datachanged', me.refresh, me);
63621                 me.store.un('add', me.delayRefresh, me);
63622                 me.store.un('remove', me.delayRefresh, me);
63623                 me.store.un('update', me.delayRefresh, me);
63624                 me.store.un('clear', me.refresh, me);
63625             }
63626         }
63627         if (store) {
63628             store = Ext.data.StoreManager.lookup(store);
63629             store.on({
63630                 scope: me,
63631                 datachanged: me.refresh,
63632                 add: me.delayRefresh,
63633                 remove: me.delayRefresh,
63634                 update: me.delayRefresh,
63635                 clear: me.refresh
63636             });
63637         }
63638         me.store = store;
63639         if (store && !initial) {
63640             me.refresh();
63641         }
63642     },
63643
63644     // @private Create Axis
63645     initializeAxis: function(axis) {
63646         var me = this,
63647             chartBBox = me.chartBBox,
63648             w = chartBBox.width,
63649             h = chartBBox.height,
63650             x = chartBBox.x,
63651             y = chartBBox.y,
63652             themeAttrs = me.themeAttrs,
63653             config = {
63654                 chart: me
63655             };
63656         if (themeAttrs) {
63657             config.axisStyle = Ext.apply({}, themeAttrs.axis);
63658             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
63659             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
63660             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
63661             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
63662             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
63663             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
63664             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
63665             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
63666         }
63667         switch (axis.position) {
63668             case 'top':
63669                 Ext.apply(config, {
63670                     length: w,
63671                     width: h,
63672                     x: x,
63673                     y: y
63674                 });
63675             break;
63676             case 'bottom':
63677                 Ext.apply(config, {
63678                     length: w,
63679                     width: h,
63680                     x: x,
63681                     y: h
63682                 });
63683             break;
63684             case 'left':
63685                 Ext.apply(config, {
63686                     length: h,
63687                     width: w,
63688                     x: x,
63689                     y: h
63690                 });
63691             break;
63692             case 'right':
63693                 Ext.apply(config, {
63694                     length: h,
63695                     width: w,
63696                     x: w,
63697                     y: h
63698                 });
63699             break;
63700         }
63701         if (!axis.chart) {
63702             Ext.apply(config, axis);
63703             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
63704         }
63705         else {
63706             Ext.apply(axis, config);
63707         }
63708     },
63709
63710
63711     /**
63712      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
63713      * for the space taken up on each side by the axes and legend.
63714      */
63715     alignAxes: function() {
63716         var me = this,
63717             axes = me.axes,
63718             legend = me.legend,
63719             edges = ['top', 'right', 'bottom', 'left'],
63720             chartBBox,
63721             insetPadding = me.insetPadding,
63722             insets = {
63723                 top: insetPadding,
63724                 right: insetPadding,
63725                 bottom: insetPadding,
63726                 left: insetPadding
63727             };
63728
63729         function getAxis(edge) {
63730             var i = axes.findIndex('position', edge);
63731             return (i < 0) ? null : axes.getAt(i);
63732         }
63733
63734         // Find the space needed by axes and legend as a positive inset from each edge
63735         Ext.each(edges, function(edge) {
63736             var isVertical = (edge === 'left' || edge === 'right'),
63737                 axis = getAxis(edge),
63738                 bbox;
63739
63740             // Add legend size if it's on this edge
63741             if (legend !== false) {
63742                 if (legend.position === edge) {
63743                     bbox = legend.getBBox();
63744                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
63745                 }
63746             }
63747
63748             // Add axis size if there's one on this edge only if it has been
63749             //drawn before.
63750             if (axis && axis.bbox) {
63751                 bbox = axis.bbox;
63752                 insets[edge] += (isVertical ? bbox.width : bbox.height);
63753             }
63754         });
63755         // Build the chart bbox based on the collected inset values
63756         chartBBox = {
63757             x: insets.left,
63758             y: insets.top,
63759             width: me.curWidth - insets.left - insets.right,
63760             height: me.curHeight - insets.top - insets.bottom
63761         };
63762         me.chartBBox = chartBBox;
63763
63764         // Go back through each axis and set its length and position based on the
63765         // corresponding edge of the chartBBox
63766         axes.each(function(axis) {
63767             var pos = axis.position,
63768                 isVertical = (pos === 'left' || pos === 'right');
63769
63770             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
63771             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
63772             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
63773             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
63774         });
63775     },
63776
63777     // @private initialize the series.
63778     initializeSeries: function(series, idx) {
63779         var me = this,
63780             themeAttrs = me.themeAttrs,
63781             seriesObj, markerObj, seriesThemes, st,
63782             markerThemes, colorArrayStyle = [],
63783             i = 0, l,
63784             config = {
63785                 chart: me,
63786                 seriesId: series.seriesId
63787             };
63788         if (themeAttrs) {
63789             seriesThemes = themeAttrs.seriesThemes;
63790             markerThemes = themeAttrs.markerThemes;
63791             seriesObj = Ext.apply({}, themeAttrs.series);
63792             markerObj = Ext.apply({}, themeAttrs.marker);
63793             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
63794             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
63795             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
63796             if (themeAttrs.colors) {
63797                 config.colorArrayStyle = themeAttrs.colors;
63798             } else {
63799                 colorArrayStyle = [];
63800                 for (l = seriesThemes.length; i < l; i++) {
63801                     st = seriesThemes[i];
63802                     if (st.fill || st.stroke) {
63803                         colorArrayStyle.push(st.fill || st.stroke);
63804                     }
63805                 }
63806                 if (colorArrayStyle.length) {
63807                     config.colorArrayStyle = colorArrayStyle;
63808                 }
63809             }
63810             config.seriesIdx = idx;
63811         }
63812         if (series instanceof Ext.chart.series.Series) {
63813             Ext.apply(series, config);
63814         } else {
63815             Ext.applyIf(config, series);
63816             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
63817         }
63818         if (series.initialize) {
63819             series.initialize();
63820         }
63821     },
63822
63823     // @private
63824     getMaxGutter: function() {
63825         var me = this,
63826             maxGutter = [0, 0];
63827         me.series.each(function(s) {
63828             var gutter = s.getGutters && s.getGutters() || [0, 0];
63829             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
63830             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
63831         });
63832         me.maxGutter = maxGutter;
63833     },
63834
63835     // @private draw axis.
63836     drawAxis: function(axis) {
63837         axis.drawAxis();
63838     },
63839
63840     // @private draw series.
63841     drawCharts: function(series) {
63842         series.triggerafterrender = false;
63843         series.drawSeries();
63844         if (!this.animate) {
63845             series.fireEvent('afterrender');
63846         }
63847     },
63848
63849     // @private remove gently.
63850     destroy: function() {
63851         Ext.destroy(this.surface);
63852         this.bindStore(null);
63853         this.callParent(arguments);
63854     }
63855 });
63856
63857 /**
63858  * @class Ext.chart.Highlight
63859  * A mixin providing highlight functionality for Ext.chart.series.Series.
63860  */
63861 Ext.define('Ext.chart.Highlight', {
63862
63863     /* Begin Definitions */
63864
63865     requires: ['Ext.fx.Anim'],
63866
63867     /* End Definitions */
63868
63869     /**
63870      * Highlight the given series item.
63871      * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
63872      * or just use default styles per series by setting highlight = true.
63873      */
63874     highlight: false,
63875
63876     highlightCfg : null,
63877
63878     constructor: function(config) {
63879         if (config.highlight) {
63880             if (config.highlight !== true) { //is an object
63881                 this.highlightCfg = Ext.apply({}, config.highlight);
63882             }
63883             else {
63884                 this.highlightCfg = {
63885                     fill: '#fdd',
63886                     radius: 20,
63887                     lineWidth: 5,
63888                     stroke: '#f55'
63889                 };
63890             }
63891         }
63892     },
63893
63894     /**
63895      * Highlight the given series item.
63896      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
63897      */
63898     highlightItem: function(item) {
63899         if (!item) {
63900             return;
63901         }
63902         
63903         var me = this,
63904             sprite = item.sprite,
63905             opts = me.highlightCfg,
63906             surface = me.chart.surface,
63907             animate = me.chart.animate,
63908             p, from, to, pi;
63909
63910         if (!me.highlight || !sprite || sprite._highlighted) {
63911             return;
63912         }
63913         if (sprite._anim) {
63914             sprite._anim.paused = true;
63915         }
63916         sprite._highlighted = true;
63917         if (!sprite._defaults) {
63918             sprite._defaults = Ext.apply({}, sprite.attr);
63919             from = {};
63920             to = {};
63921             for (p in opts) {
63922                 if (! (p in sprite._defaults)) {
63923                     sprite._defaults[p] = surface.availableAttrs[p];
63924                 }
63925                 from[p] = sprite._defaults[p];
63926                 to[p] = opts[p];
63927                 if (Ext.isObject(opts[p])) {
63928                     from[p] = {};
63929                     to[p] = {};
63930                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
63931                     Ext.apply(from[p], sprite._defaults[p]);
63932                     for (pi in sprite._defaults[p]) {
63933                         if (! (pi in opts[p])) {
63934                             to[p][pi] = from[p][pi];
63935                         } else {
63936                             to[p][pi] = opts[p][pi];
63937                         }
63938                     }
63939                     for (pi in opts[p]) {
63940                         if (! (pi in to[p])) {
63941                             to[p][pi] = opts[p][pi];
63942                         }
63943                     }
63944                 }
63945             }
63946             sprite._from = from;
63947             sprite._to = to;
63948             sprite._endStyle = to;
63949         }
63950         if (animate) {
63951             sprite._anim = Ext.create('Ext.fx.Anim', {
63952                 target: sprite,
63953                 from: sprite._from,
63954                 to: sprite._to,
63955                 duration: 150
63956             });
63957         } else {
63958             sprite.setAttributes(sprite._to, true);
63959         }
63960     },
63961
63962     /**
63963      * Un-highlight any existing highlights
63964      */
63965     unHighlightItem: function() {
63966         if (!this.highlight || !this.items) {
63967             return;
63968         }
63969
63970         var me = this,
63971             items = me.items,
63972             len = items.length,
63973             opts = me.highlightCfg,
63974             animate = me.chart.animate,
63975             i = 0,
63976             obj, p, sprite;
63977
63978         for (; i < len; i++) {
63979             if (!items[i]) {
63980                 continue;
63981             }
63982             sprite = items[i].sprite;
63983             if (sprite && sprite._highlighted) {
63984                 if (sprite._anim) {
63985                     sprite._anim.paused = true;
63986                 }
63987                 obj = {};
63988                 for (p in opts) {
63989                     if (Ext.isObject(sprite._defaults[p])) {
63990                         obj[p] = {};
63991                         Ext.apply(obj[p], sprite._defaults[p]);
63992                     }
63993                     else {
63994                         obj[p] = sprite._defaults[p];
63995                     }
63996                 }
63997                 if (animate) {
63998                     //sprite._to = obj;
63999                     sprite._endStyle = obj;
64000                     sprite._anim = Ext.create('Ext.fx.Anim', {
64001                         target: sprite,
64002                         to: obj,
64003                         duration: 150
64004                     });
64005                 }
64006                 else {
64007                     sprite.setAttributes(obj, true);
64008                 }
64009                 delete sprite._highlighted;
64010                 //delete sprite._defaults;
64011             }
64012         }
64013     },
64014
64015     cleanHighlights: function() {
64016         if (!this.highlight) {
64017             return;
64018         }
64019
64020         var group = this.group,
64021             markerGroup = this.markerGroup,
64022             i = 0,
64023             l;
64024         for (l = group.getCount(); i < l; i++) {
64025             delete group.getAt(i)._defaults;
64026         }
64027         if (markerGroup) {
64028             for (l = markerGroup.getCount(); i < l; i++) {
64029                 delete markerGroup.getAt(i)._defaults;
64030             }
64031         }
64032     }
64033 });
64034 /**
64035  * @class Ext.chart.Label
64036  *
64037  * Labels is a mixin to the Series class. Labels methods are implemented
64038  * in each of the Series (Pie, Bar, etc) for label creation and placement.
64039  *
64040  * The methods implemented by the Series are:
64041  *
64042  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
64043  *   The arguments of the method are:
64044  *   - *`storeItem`* The element of the store that is related to the label sprite.
64045  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
64046  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
64047  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
64048  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
64049  *
64050  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
64051  *    The arguments of the method are:
64052  *    - *`label`* The sprite label.</li>
64053  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
64054  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
64055  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
64056  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
64057  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
64058  *    - *`animate`* A boolean value to set or unset animations for the labels.
64059  */
64060 Ext.define('Ext.chart.Label', {
64061
64062     /* Begin Definitions */
64063
64064     requires: ['Ext.draw.Color'],
64065
64066     /* End Definitions */
64067
64068     /**
64069      * @cfg {Object} label
64070      * Object with the following properties:
64071      *
64072      * - **display** : String
64073      *
64074      *   Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
64075      *   "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
64076      *   Default value: 'none'.
64077      *
64078      * - **color** : String
64079      *
64080      *   The color of the label text.
64081      *   Default value: '#000' (black).
64082      *
64083      * - **contrast** : Boolean
64084      *
64085      *   True to render the label in contrasting color with the backround.
64086      *   Default value: false.
64087      *
64088      * - **field** : String
64089      *
64090      *   The name of the field to be displayed in the label.
64091      *   Default value: 'name'.
64092      *
64093      * - **minMargin** : Number
64094      *
64095      *   Specifies the minimum distance from a label to the origin of the visualization.
64096      *   This parameter is useful when using PieSeries width variable pie slice lengths.
64097      *   Default value: 50.
64098      *
64099      * - **font** : String
64100      *
64101      *   The font used for the labels.
64102      *   Default value: "11px Helvetica, sans-serif".
64103      *
64104      * - **orientation** : String
64105      *
64106      *   Either "horizontal" or "vertical".
64107      *   Dafault value: "horizontal".
64108      *
64109      * - **renderer** : Function
64110      *
64111      *   Optional function for formatting the label into a displayable value.
64112      *   Default value: function(v) { return v; }
64113      */
64114
64115     //@private a regex to parse url type colors.
64116     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
64117
64118     //@private the mixin constructor. Used internally by Series.
64119     constructor: function(config) {
64120         var me = this;
64121         me.label = Ext.applyIf(me.label || {},
64122         {
64123             display: "none",
64124             color: "#000",
64125             field: "name",
64126             minMargin: 50,
64127             font: "11px Helvetica, sans-serif",
64128             orientation: "horizontal",
64129             renderer: function(v) {
64130                 return v;
64131             }
64132         });
64133
64134         if (me.label.display !== 'none') {
64135             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
64136         }
64137     },
64138
64139     //@private a method to render all labels in the labelGroup
64140     renderLabels: function() {
64141         var me = this,
64142             chart = me.chart,
64143             gradients = chart.gradients,
64144             items = me.items,
64145             animate = chart.animate,
64146             config = me.label,
64147             display = config.display,
64148             color = config.color,
64149             field = [].concat(config.field),
64150             group = me.labelsGroup,
64151             groupLength = (group || 0) && group.length,
64152             store = me.chart.store,
64153             len = store.getCount(),
64154             itemLength = (items || 0) && items.length,
64155             ratio = itemLength / len,
64156             gradientsCount = (gradients || 0) && gradients.length,
64157             Color = Ext.draw.Color,
64158             hides = [],
64159             gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
64160             storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
64161
64162         if (display == 'none') {
64163             return;
64164         }
64165         // no items displayed, hide all labels
64166         if(itemLength == 0){
64167             while(groupLength--)
64168                 hides.push(groupLength);
64169         }else{
64170             for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
64171                 index = 0;
64172                 for (j = 0; j < ratio; j++) {
64173                     item = items[count];
64174                     label = group.getAt(groupIndex);
64175                     storeItem = store.getAt(i);
64176                     //check the excludes
64177                     while(this.__excludes && this.__excludes[index] && ratio > 1) {
64178                         if(field[j]){
64179                             hides.push(groupIndex);
64180                         }
64181                         index++;
64182
64183                     }
64184
64185                     if (!item && label) {
64186                         label.hide(true);
64187                         groupIndex++;
64188                     }
64189
64190                     if (item && field[j]) {
64191                         if (!label) {
64192                             label = me.onCreateLabel(storeItem, item, i, display, j, index);
64193                         }
64194                         me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
64195                         groupIndex++;
64196
64197                         //set contrast
64198                         if (config.contrast && item.sprite) {
64199                             sprite = item.sprite;
64200                             //set the color string to the color to be set.
64201                             if (sprite._endStyle) {
64202                                 colorString = sprite._endStyle.fill;
64203                             }
64204                             else if (sprite._to) {
64205                                 colorString = sprite._to.fill;
64206                             }
64207                             else {
64208                                 colorString = sprite.attr.fill;
64209                             }
64210                             colorString = colorString || sprite.attr.fill;
64211
64212                             spriteColor = Color.fromString(colorString);
64213                             //color wasn't parsed property maybe because it's a gradient id
64214                             if (colorString && !spriteColor) {
64215                                 colorString = colorString.match(me.colorStringRe)[1];
64216                                 for (k = 0; k < gradientsCount; k++) {
64217                                     gradient = gradients[k];
64218                                     if (gradient.id == colorString) {
64219                                         //avg color stops
64220                                         colorStop = 0; colorStopTotal = 0;
64221                                         for (colorStopIndex in gradient.stops) {
64222                                             colorStop++;
64223                                             colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
64224                                         }
64225                                         spriteBrightness = (colorStopTotal / colorStop) / 255;
64226                                         break;
64227                                     }
64228                                 }
64229                             }
64230                             else {
64231                                 spriteBrightness = spriteColor.getGrayscale() / 255;
64232                             }
64233                             if (label.isOutside) {
64234                                 spriteBrightness = 1;
64235                             }
64236                             labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
64237                             labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
64238                             label.setAttributes({
64239                                 fill: String(Color.fromHSL.apply({}, labelColor))
64240                             }, true);
64241                         }
64242
64243                     }
64244                     count++;
64245                     index++;
64246                 }
64247             }
64248         }
64249         me.hideLabels(hides);
64250     },
64251     hideLabels: function(hides){
64252         var labelsGroup = this.labelsGroup,
64253             hlen = hides.length;
64254         while(hlen--)
64255             labelsGroup.getAt(hides[hlen]).hide(true);
64256     }
64257 });
64258 Ext.define('Ext.chart.MaskLayer', {
64259     extend: 'Ext.Component',
64260     
64261     constructor: function(config) {
64262         config = Ext.apply(config || {}, {
64263             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
64264         });
64265         this.callParent([config]);    
64266     },
64267     
64268     initComponent: function() {
64269         var me = this;
64270         me.callParent(arguments);
64271         me.addEvents(
64272             'mousedown',
64273             'mouseup',
64274             'mousemove',
64275             'mouseenter',
64276             'mouseleave'
64277         );
64278     },
64279
64280     initDraggable: function() {
64281         this.callParent(arguments);
64282         this.dd.onStart = function (e) {
64283             var me = this,
64284                 comp = me.comp;
64285     
64286             // Cache the start [X, Y] array
64287             this.startPosition = comp.getPosition(true);
64288     
64289             // If client Component has a ghost method to show a lightweight version of itself
64290             // then use that as a drag proxy unless configured to liveDrag.
64291             if (comp.ghost && !comp.liveDrag) {
64292                  me.proxy = comp.ghost();
64293                  me.dragTarget = me.proxy.header.el;
64294             }
64295     
64296             // Set the constrainTo Region before we start dragging.
64297             if (me.constrain || me.constrainDelegate) {
64298                 me.constrainTo = me.calculateConstrainRegion();
64299             }
64300         };
64301     }
64302 });
64303 /**
64304  * @class Ext.chart.TipSurface
64305  * @ignore
64306  */
64307 Ext.define('Ext.chart.TipSurface', {
64308
64309     /* Begin Definitions */
64310
64311     extend: 'Ext.draw.Component',
64312
64313     /* End Definitions */
64314
64315     spriteArray: false,
64316     renderFirst: true,
64317
64318     constructor: function(config) {
64319         this.callParent([config]);
64320         if (config.sprites) {
64321             this.spriteArray = [].concat(config.sprites);
64322             delete config.sprites;
64323         }
64324     },
64325
64326     onRender: function() {
64327         var me = this,
64328             i = 0,
64329             l = 0,
64330             sp,
64331             sprites;
64332             this.callParent(arguments);
64333         sprites = me.spriteArray;
64334         if (me.renderFirst && sprites) {
64335             me.renderFirst = false;
64336             for (l = sprites.length; i < l; i++) {
64337                 sp = me.surface.add(sprites[i]);
64338                 sp.setAttributes({
64339                     hidden: false
64340                 },
64341                 true);
64342             }
64343         }
64344     }
64345 });
64346
64347 /**
64348  * @class Ext.chart.Tip
64349  * Provides tips for Ext.chart.series.Series.
64350  */
64351 Ext.define('Ext.chart.Tip', {
64352
64353     /* Begin Definitions */
64354
64355     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
64356
64357     /* End Definitions */
64358
64359     constructor: function(config) {
64360         var me = this,
64361             surface,
64362             sprites,
64363             tipSurface;
64364         if (config.tips) {
64365             me.tipTimeout = null;
64366             me.tipConfig = Ext.apply({}, config.tips, {
64367                 renderer: Ext.emptyFn,
64368                 constrainPosition: false
64369             });
64370             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
64371             me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
64372             me.chart.surface.on('mouseleave', function() {
64373                 me.hideTip();
64374             });
64375             if (me.tipConfig.surface) {
64376                 //initialize a surface
64377                 surface = me.tipConfig.surface;
64378                 sprites = surface.sprites;
64379                 tipSurface = Ext.create('Ext.chart.TipSurface', {
64380                     id: 'tipSurfaceComponent',
64381                     sprites: sprites
64382                 });
64383                 if (surface.width && surface.height) {
64384                     tipSurface.setSize(surface.width, surface.height);
64385                 }
64386                 me.tooltip.add(tipSurface);
64387                 me.spriteTip = tipSurface;
64388             }
64389         }
64390     },
64391
64392     showTip: function(item) {
64393         var me = this;
64394         if (!me.tooltip) {
64395             return;
64396         }
64397         clearTimeout(me.tipTimeout);
64398         var tooltip = me.tooltip,
64399             spriteTip = me.spriteTip,
64400             tipConfig = me.tipConfig,
64401             trackMouse = tooltip.trackMouse,
64402             sprite, surface, surfaceExt, pos, x, y;
64403         if (!trackMouse) {
64404             tooltip.trackMouse = true;
64405             sprite = item.sprite;
64406             surface = sprite.surface;
64407             surfaceExt = Ext.get(surface.getId());
64408             if (surfaceExt) {
64409                 pos = surfaceExt.getXY();
64410                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
64411                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
64412                 tooltip.targetXY = [x, y];
64413             }
64414         }
64415         if (spriteTip) {
64416             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
64417         } else {
64418             tipConfig.renderer.call(tooltip, item.storeItem, item);
64419         }
64420         tooltip.show();
64421         tooltip.trackMouse = trackMouse;
64422     },
64423
64424     hideTip: function(item) {
64425         var tooltip = this.tooltip;
64426         if (!tooltip) {
64427             return;
64428         }
64429         clearTimeout(this.tipTimeout);
64430         this.tipTimeout = setTimeout(function() {
64431             tooltip.hide();
64432         }, 0);
64433     }
64434 });
64435 /**
64436  * @class Ext.chart.axis.Abstract
64437  * Base class for all axis classes.
64438  * @private
64439  */
64440 Ext.define('Ext.chart.axis.Abstract', {
64441
64442     /* Begin Definitions */
64443
64444     requires: ['Ext.chart.Chart'],
64445
64446     /* End Definitions */
64447
64448     /**
64449      * Creates new Axis.
64450      * @param {Object} config (optional) Config options.
64451      */
64452     constructor: function(config) {
64453         config = config || {};
64454
64455         var me = this,
64456             pos = config.position || 'left';
64457
64458         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
64459         //axisLabel(Top|Bottom|Right|Left)Style
64460         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
64461         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
64462         Ext.apply(me, config);
64463         me.fields = [].concat(me.fields);
64464         this.callParent();
64465         me.labels = [];
64466         me.getId();
64467         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
64468     },
64469
64470     alignment: null,
64471     grid: false,
64472     steps: 10,
64473     x: 0,
64474     y: 0,
64475     minValue: 0,
64476     maxValue: 0,
64477
64478     getId: function() {
64479         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
64480     },
64481
64482     /*
64483       Called to process a view i.e to make aggregation and filtering over
64484       a store creating a substore to be used to render the axis. Since many axes
64485       may do different things on the data and we want the final result of all these
64486       operations to be rendered we need to call processView on all axes before drawing
64487       them.
64488     */
64489     processView: Ext.emptyFn,
64490
64491     drawAxis: Ext.emptyFn,
64492     addDisplayAndLabels: Ext.emptyFn
64493 });
64494
64495 /**
64496  * @class Ext.chart.axis.Axis
64497  * @extends Ext.chart.axis.Abstract
64498  *
64499  * Defines axis for charts. The axis position, type, style can be configured.
64500  * The axes are defined in an axes array of configuration objects where the type,
64501  * field, grid and other configuration options can be set. To know more about how
64502  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
64503  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
64504  *
64505  *     axes: [{
64506  *         type: 'Numeric',
64507  *         grid: true,
64508  *         position: 'left',
64509  *         fields: ['data1', 'data2', 'data3'],
64510  *         title: 'Number of Hits',
64511  *         grid: {
64512  *             odd: {
64513  *                 opacity: 1,
64514  *                 fill: '#ddd',
64515  *                 stroke: '#bbb',
64516  *                 'stroke-width': 1
64517  *             }
64518  *         },
64519  *         minimum: 0
64520  *     }, {
64521  *         type: 'Category',
64522  *         position: 'bottom',
64523  *         fields: ['name'],
64524  *         title: 'Month of the Year',
64525  *         grid: true,
64526  *         label: {
64527  *             rotate: {
64528  *                 degrees: 315
64529  *             }
64530  *         }
64531  *     }]
64532  *
64533  * 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
64534  * 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.
64535  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
64536  * category axis the labels will be rotated so they can fit the space better.
64537  */
64538 Ext.define('Ext.chart.axis.Axis', {
64539
64540     /* Begin Definitions */
64541
64542     extend: 'Ext.chart.axis.Abstract',
64543
64544     alternateClassName: 'Ext.chart.Axis',
64545
64546     requires: ['Ext.draw.Draw'],
64547
64548     /* End Definitions */
64549
64550     /**
64551      * @cfg {Boolean/Object} grid
64552      * The grid configuration enables you to set a background grid for an axis.
64553      * If set to *true* on a vertical axis, vertical lines will be drawn.
64554      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
64555      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
64556      *
64557      * You can set specific options for the grid configuration for odd and/or even lines/rows.
64558      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
64559      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
64560      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
64561      *
64562      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
64563      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
64564      *
64565      * For example:
64566      *
64567      *     axes: [{
64568      *         type: 'Numeric',
64569      *         grid: true,
64570      *         position: 'left',
64571      *         fields: ['data1', 'data2', 'data3'],
64572      *         title: 'Number of Hits',
64573      *         grid: {
64574      *             odd: {
64575      *                 opacity: 1,
64576      *                 fill: '#ddd',
64577      *                 stroke: '#bbb',
64578      *                 'stroke-width': 1
64579      *             }
64580      *         }
64581      *     }, {
64582      *         type: 'Category',
64583      *         position: 'bottom',
64584      *         fields: ['name'],
64585      *         title: 'Month of the Year',
64586      *         grid: true
64587      *     }]
64588      *
64589      */
64590
64591     /**
64592      * @cfg {Number} majorTickSteps
64593      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
64594      */
64595
64596     /**
64597      * @cfg {Number} minorTickSteps
64598      * The number of small ticks between two major ticks. Default is zero.
64599      */
64600
64601     /**
64602      * @cfg {String} title
64603      * The title for the Axis
64604      */
64605
64606     //@private force min/max values from store
64607     forceMinMax: false,
64608
64609     /**
64610      * @cfg {Number} dashSize
64611      * The size of the dash marker. Default's 3.
64612      */
64613     dashSize: 3,
64614
64615     /**
64616      * @cfg {String} position
64617      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
64618      */
64619     position: 'bottom',
64620
64621     // @private
64622     skipFirst: false,
64623
64624     /**
64625      * @cfg {Number} length
64626      * Offset axis position. Default's 0.
64627      */
64628     length: 0,
64629
64630     /**
64631      * @cfg {Number} width
64632      * Offset axis width. Default's 0.
64633      */
64634     width: 0,
64635
64636     majorTickSteps: false,
64637
64638     // @private
64639     applyData: Ext.emptyFn,
64640
64641     getRange: function () {
64642         var me = this,
64643             store = me.chart.getChartStore(),
64644             fields = me.fields,
64645             ln = fields.length,
64646             math = Math,
64647             mmax = math.max,
64648             mmin = math.min,
64649             aggregate = false,
64650             min = isNaN(me.minimum) ? Infinity : me.minimum,
64651             max = isNaN(me.maximum) ? -Infinity : me.maximum,
64652             total = 0, i, l, value, values, rec,
64653             excludes = [],
64654             series = me.chart.series.items;
64655
64656         //if one series is stacked I have to aggregate the values
64657         //for the scale.
64658         // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
64659         // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
64660         // Assuming only stack on y-axis.
64661         // CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since 
64662         // it was constraining the minmax calculation to y-axis stacked
64663         // visualizations.
64664         for (i = 0, l = series.length; !aggregate && i < l; i++) {
64665             aggregate = aggregate || series[i].stacked;
64666             excludes = series[i].__excludes || excludes;
64667         }
64668         store.each(function(record) {
64669             if (aggregate) {
64670                 if (!isFinite(min)) {
64671                     min = 0;
64672                 }
64673                 for (values = [0, 0], i = 0; i < ln; i++) {
64674                     if (excludes[i]) {
64675                         continue;
64676                     }
64677                     rec = record.get(fields[i]);
64678                     values[+(rec > 0)] += math.abs(rec);
64679                 }
64680                 max = mmax(max, -values[0], +values[1]);
64681                 min = mmin(min, -values[0], +values[1]);
64682             }
64683             else {
64684                 for (i = 0; i < ln; i++) {
64685                     if (excludes[i]) {
64686                         continue;
64687                     }
64688                     value = record.get(fields[i]);
64689                     max = mmax(max, +value);
64690                     min = mmin(min, +value);
64691                 }
64692             }
64693         });
64694         if (!isFinite(max)) {
64695             max = me.prevMax || 0;
64696         }
64697         if (!isFinite(min)) {
64698             min = me.prevMin || 0;
64699         }
64700         //normalize min max for snapEnds.
64701         if (min != max && (max != Math.floor(max))) {
64702             max = Math.floor(max) + 1;
64703         }
64704
64705         if (!isNaN(me.minimum)) {
64706             min = me.minimum;
64707         }
64708         
64709         if (!isNaN(me.maximum)) {
64710             max = me.maximum;
64711         }
64712
64713         return {min: min, max: max};
64714     },
64715
64716     // @private creates a structure with start, end and step points.
64717     calcEnds: function() {
64718         var me = this,
64719             fields = me.fields,
64720             range = me.getRange(),
64721             min = range.min,
64722             max = range.max,
64723             outfrom, outto, out;
64724
64725         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
64726         outfrom = out.from;
64727         outto = out.to;
64728         if (me.forceMinMax) {
64729             if (!isNaN(max)) {
64730                 out.to = max;
64731             }
64732             if (!isNaN(min)) {
64733                 out.from = min;
64734             }
64735         }
64736         if (!isNaN(me.maximum)) {
64737             //TODO(nico) users are responsible for their own minimum/maximum values set.
64738             //Clipping should be added to remove lines in the chart which are below the axis.
64739             out.to = me.maximum;
64740         }
64741         if (!isNaN(me.minimum)) {
64742             //TODO(nico) users are responsible for their own minimum/maximum values set.
64743             //Clipping should be added to remove lines in the chart which are below the axis.
64744             out.from = me.minimum;
64745         }
64746
64747         //Adjust after adjusting minimum and maximum
64748         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
64749
64750         if (me.adjustMaximumByMajorUnit) {
64751             out.to += out.step;
64752         }
64753         if (me.adjustMinimumByMajorUnit) {
64754             out.from -= out.step;
64755         }
64756         me.prevMin = min == max? 0 : min;
64757         me.prevMax = max;
64758         return out;
64759     },
64760
64761     /**
64762      * Renders the axis into the screen and updates its position.
64763      */
64764     drawAxis: function (init) {
64765         var me = this,
64766             i, j,
64767             x = me.x,
64768             y = me.y,
64769             gutterX = me.chart.maxGutter[0],
64770             gutterY = me.chart.maxGutter[1],
64771             dashSize = me.dashSize,
64772             subDashesX = me.minorTickSteps || 0,
64773             subDashesY = me.minorTickSteps || 0,
64774             length = me.length,
64775             position = me.position,
64776             inflections = [],
64777             calcLabels = false,
64778             stepCalcs = me.applyData(),
64779             step = stepCalcs.step,
64780             steps = stepCalcs.steps,
64781             from = stepCalcs.from,
64782             to = stepCalcs.to,
64783             trueLength,
64784             currentX,
64785             currentY,
64786             path,
64787             prev,
64788             dashesX,
64789             dashesY,
64790             delta;
64791
64792         //If no steps are specified
64793         //then don't draw the axis. This generally happens
64794         //when an empty store.
64795         if (me.hidden || isNaN(step) || (from == to)) {
64796             return;
64797         }
64798
64799         me.from = stepCalcs.from;
64800         me.to = stepCalcs.to;
64801         if (position == 'left' || position == 'right') {
64802             currentX = Math.floor(x) + 0.5;
64803             path = ["M", currentX, y, "l", 0, -length];
64804             trueLength = length - (gutterY * 2);
64805         }
64806         else {
64807             currentY = Math.floor(y) + 0.5;
64808             path = ["M", x, currentY, "l", length, 0];
64809             trueLength = length - (gutterX * 2);
64810         }
64811
64812         delta = trueLength / (steps || 1);
64813         dashesX = Math.max(subDashesX +1, 0);
64814         dashesY = Math.max(subDashesY +1, 0);
64815         if (me.type == 'Numeric' || me.type == 'Time') {
64816             calcLabels = true;
64817             me.labels = [stepCalcs.from];
64818         }
64819         if (position == 'right' || position == 'left') {
64820             currentY = y - gutterY;
64821             currentX = x - ((position == 'left') * dashSize * 2);
64822             while (currentY >= y - gutterY - trueLength) {
64823                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
64824                 if (currentY != y - gutterY) {
64825                     for (i = 1; i < dashesY; i++) {
64826                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64827                     }
64828                 }
64829                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64830                 currentY -= delta;
64831                 if (calcLabels) {
64832                     me.labels.push(me.labels[me.labels.length -1] + step);
64833                 }
64834                 if (delta === 0) {
64835                     break;
64836                 }
64837             }
64838             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
64839                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
64840                 for (i = 1; i < dashesY; i++) {
64841                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64842                 }
64843                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64844                 if (calcLabels) {
64845                     me.labels.push(me.labels[me.labels.length -1] + step);
64846                 }
64847             }
64848         } else {
64849             currentX = x + gutterX;
64850             currentY = y - ((position == 'top') * dashSize * 2);
64851             while (currentX <= x + gutterX + trueLength) {
64852                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64853                 if (currentX != x + gutterX) {
64854                     for (i = 1; i < dashesX; i++) {
64855                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64856                     }
64857                 }
64858                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64859                 currentX += delta;
64860                 if (calcLabels) {
64861                     me.labels.push(me.labels[me.labels.length -1] + step);
64862                 }
64863                 if (delta === 0) {
64864                     break;
64865                 }
64866             }
64867             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
64868                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64869                 for (i = 1; i < dashesX; i++) {
64870                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64871                 }
64872                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64873                 if (calcLabels) {
64874                     me.labels.push(me.labels[me.labels.length -1] + step);
64875                 }
64876             }
64877         }
64878         if (!me.axis) {
64879             me.axis = me.chart.surface.add(Ext.apply({
64880                 type: 'path',
64881                 path: path
64882             }, me.axisStyle));
64883         }
64884         me.axis.setAttributes({
64885             path: path
64886         }, true);
64887         me.inflections = inflections;
64888         if (!init && me.grid) {
64889             me.drawGrid();
64890         }
64891         me.axisBBox = me.axis.getBBox();
64892         me.drawLabel();
64893     },
64894
64895     /**
64896      * Renders an horizontal and/or vertical grid into the Surface.
64897      */
64898     drawGrid: function() {
64899         var me = this,
64900             surface = me.chart.surface,
64901             grid = me.grid,
64902             odd = grid.odd,
64903             even = grid.even,
64904             inflections = me.inflections,
64905             ln = inflections.length - ((odd || even)? 0 : 1),
64906             position = me.position,
64907             gutter = me.chart.maxGutter,
64908             width = me.width - 2,
64909             vert = false,
64910             point, prevPoint,
64911             i = 1,
64912             path = [], styles, lineWidth, dlineWidth,
64913             oddPath = [], evenPath = [];
64914
64915         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
64916             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
64917             i = 0;
64918             ln++;
64919         }
64920         for (; i < ln; i++) {
64921             point = inflections[i];
64922             prevPoint = inflections[i - 1];
64923             if (odd || even) {
64924                 path = (i % 2)? oddPath : evenPath;
64925                 styles = ((i % 2)? odd : even) || {};
64926                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
64927                 dlineWidth = 2 * lineWidth;
64928                 if (position == 'left') {
64929                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64930                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64931                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
64932                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
64933                 }
64934                 else if (position == 'right') {
64935                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64936                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64937                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
64938                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
64939                 }
64940                 else if (position == 'top') {
64941                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
64942                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
64943                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
64944                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
64945                 }
64946                 else {
64947                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
64948                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
64949                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
64950                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
64951                 }
64952             } else {
64953                 if (position == 'left') {
64954                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
64955                 }
64956                 else if (position == 'right') {
64957                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
64958                 }
64959                 else if (position == 'top') {
64960                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
64961                 }
64962                 else {
64963                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
64964                 }
64965             }
64966         }
64967         if (odd || even) {
64968             if (oddPath.length) {
64969                 if (!me.gridOdd && oddPath.length) {
64970                     me.gridOdd = surface.add({
64971                         type: 'path',
64972                         path: oddPath
64973                     });
64974                 }
64975                 me.gridOdd.setAttributes(Ext.apply({
64976                     path: oddPath,
64977                     hidden: false
64978                 }, odd || {}), true);
64979             }
64980             if (evenPath.length) {
64981                 if (!me.gridEven) {
64982                     me.gridEven = surface.add({
64983                         type: 'path',
64984                         path: evenPath
64985                     });
64986                 }
64987                 me.gridEven.setAttributes(Ext.apply({
64988                     path: evenPath,
64989                     hidden: false
64990                 }, even || {}), true);
64991             }
64992         }
64993         else {
64994             if (path.length) {
64995                 if (!me.gridLines) {
64996                     me.gridLines = me.chart.surface.add({
64997                         type: 'path',
64998                         path: path,
64999                         "stroke-width": me.lineWidth || 1,
65000                         stroke: me.gridColor || '#ccc'
65001                     });
65002                 }
65003                 me.gridLines.setAttributes({
65004                     hidden: false,
65005                     path: path
65006                 }, true);
65007             }
65008             else if (me.gridLines) {
65009                 me.gridLines.hide(true);
65010             }
65011         }
65012     },
65013
65014     //@private
65015     getOrCreateLabel: function(i, text) {
65016         var me = this,
65017             labelGroup = me.labelGroup,
65018             textLabel = labelGroup.getAt(i),
65019             surface = me.chart.surface;
65020         if (textLabel) {
65021             if (text != textLabel.attr.text) {
65022                 textLabel.setAttributes(Ext.apply({
65023                     text: text
65024                 }, me.label), true);
65025                 textLabel._bbox = textLabel.getBBox();
65026             }
65027         }
65028         else {
65029             textLabel = surface.add(Ext.apply({
65030                 group: labelGroup,
65031                 type: 'text',
65032                 x: 0,
65033                 y: 0,
65034                 text: text
65035             }, me.label));
65036             surface.renderItem(textLabel);
65037             textLabel._bbox = textLabel.getBBox();
65038         }
65039         //get untransformed bounding box
65040         if (me.label.rotation) {
65041             textLabel.setAttributes({
65042                 rotation: {
65043                     degrees: 0
65044                 }
65045             }, true);
65046             textLabel._ubbox = textLabel.getBBox();
65047             textLabel.setAttributes(me.label, true);
65048         } else {
65049             textLabel._ubbox = textLabel._bbox;
65050         }
65051         return textLabel;
65052     },
65053
65054     rect2pointArray: function(sprite) {
65055         var surface = this.chart.surface,
65056             rect = surface.getBBox(sprite, true),
65057             p1 = [rect.x, rect.y],
65058             p1p = p1.slice(),
65059             p2 = [rect.x + rect.width, rect.y],
65060             p2p = p2.slice(),
65061             p3 = [rect.x + rect.width, rect.y + rect.height],
65062             p3p = p3.slice(),
65063             p4 = [rect.x, rect.y + rect.height],
65064             p4p = p4.slice(),
65065             matrix = sprite.matrix;
65066         //transform the points
65067         p1[0] = matrix.x.apply(matrix, p1p);
65068         p1[1] = matrix.y.apply(matrix, p1p);
65069
65070         p2[0] = matrix.x.apply(matrix, p2p);
65071         p2[1] = matrix.y.apply(matrix, p2p);
65072
65073         p3[0] = matrix.x.apply(matrix, p3p);
65074         p3[1] = matrix.y.apply(matrix, p3p);
65075
65076         p4[0] = matrix.x.apply(matrix, p4p);
65077         p4[1] = matrix.y.apply(matrix, p4p);
65078         return [p1, p2, p3, p4];
65079     },
65080
65081     intersect: function(l1, l2) {
65082         var r1 = this.rect2pointArray(l1),
65083             r2 = this.rect2pointArray(l2);
65084         return !!Ext.draw.Draw.intersect(r1, r2).length;
65085     },
65086
65087     drawHorizontalLabels: function() {
65088        var  me = this,
65089             labelConf = me.label,
65090             floor = Math.floor,
65091             max = Math.max,
65092             axes = me.chart.axes,
65093             position = me.position,
65094             inflections = me.inflections,
65095             ln = inflections.length,
65096             labels = me.labels,
65097             labelGroup = me.labelGroup,
65098             maxHeight = 0,
65099             ratio,
65100             gutterY = me.chart.maxGutter[1],
65101             ubbox, bbox, point, prevX, prevLabel,
65102             projectedWidth = 0,
65103             textLabel, attr, textRight, text,
65104             label, last, x, y, i, firstLabel;
65105
65106         last = ln - 1;
65107         //get a reference to the first text label dimensions
65108         point = inflections[0];
65109         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
65110         ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
65111
65112         for (i = 0; i < ln; i++) {
65113             point = inflections[i];
65114             text = me.label.renderer(labels[i]);
65115             textLabel = me.getOrCreateLabel(i, text);
65116             bbox = textLabel._bbox;
65117             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
65118             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
65119             if (me.chart.maxGutter[0] == 0) {
65120                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
65121                     x = point[0];
65122                 }
65123                 else if (i == last && axes.findIndex('position', 'right') == -1) {
65124                     x = point[0] - bbox.width;
65125                 }
65126             }
65127             if (position == 'top') {
65128                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
65129             }
65130             else {
65131                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
65132             }
65133
65134             textLabel.setAttributes({
65135                 hidden: false,
65136                 x: x,
65137                 y: y
65138             }, true);
65139
65140             // Skip label if there isn't available minimum space
65141             if (i != 0 && (me.intersect(textLabel, prevLabel)
65142                 || me.intersect(textLabel, firstLabel))) {
65143                 textLabel.hide(true);
65144                 continue;
65145             }
65146
65147             prevLabel = textLabel;
65148         }
65149
65150         return maxHeight;
65151     },
65152
65153     drawVerticalLabels: function() {
65154         var me = this,
65155             inflections = me.inflections,
65156             position = me.position,
65157             ln = inflections.length,
65158             labels = me.labels,
65159             maxWidth = 0,
65160             max = Math.max,
65161             floor = Math.floor,
65162             ceil = Math.ceil,
65163             axes = me.chart.axes,
65164             gutterY = me.chart.maxGutter[1],
65165             ubbox, bbox, point, prevLabel,
65166             projectedWidth = 0,
65167             textLabel, attr, textRight, text,
65168             label, last, x, y, i;
65169
65170         last = ln;
65171         for (i = 0; i < last; i++) {
65172             point = inflections[i];
65173             text = me.label.renderer(labels[i]);
65174             textLabel = me.getOrCreateLabel(i, text);
65175             bbox = textLabel._bbox;
65176
65177             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
65178             y = point[1];
65179             if (gutterY < bbox.height / 2) {
65180                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
65181                     y = me.y - me.length + ceil(bbox.height / 2);
65182                 }
65183                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
65184                     y = me.y - floor(bbox.height / 2);
65185                 }
65186             }
65187             if (position == 'left') {
65188                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
65189             }
65190             else {
65191                 x = point[0] + me.dashSize + me.label.padding + 2;
65192             }
65193             textLabel.setAttributes(Ext.apply({
65194                 hidden: false,
65195                 x: x,
65196                 y: y
65197             }, me.label), true);
65198             // Skip label if there isn't available minimum space
65199             if (i != 0 && me.intersect(textLabel, prevLabel)) {
65200                 textLabel.hide(true);
65201                 continue;
65202             }
65203             prevLabel = textLabel;
65204         }
65205
65206         return maxWidth;
65207     },
65208
65209     /**
65210      * Renders the labels in the axes.
65211      */
65212     drawLabel: function() {
65213         var me = this,
65214             position = me.position,
65215             labelGroup = me.labelGroup,
65216             inflections = me.inflections,
65217             maxWidth = 0,
65218             maxHeight = 0,
65219             ln, i;
65220
65221         if (position == 'left' || position == 'right') {
65222             maxWidth = me.drawVerticalLabels();
65223         } else {
65224             maxHeight = me.drawHorizontalLabels();
65225         }
65226
65227         // Hide unused bars
65228         ln = labelGroup.getCount();
65229         i = inflections.length;
65230         for (; i < ln; i++) {
65231             labelGroup.getAt(i).hide(true);
65232         }
65233
65234         me.bbox = {};
65235         Ext.apply(me.bbox, me.axisBBox);
65236         me.bbox.height = maxHeight;
65237         me.bbox.width = maxWidth;
65238         if (Ext.isString(me.title)) {
65239             me.drawTitle(maxWidth, maxHeight);
65240         }
65241     },
65242
65243     // @private creates the elipsis for the text.
65244     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
65245         var bbox,
65246             x;
65247
65248         if (desiredWidth < minWidth) {
65249             sprite.hide(true);
65250             return false;
65251         }
65252         while (text.length > 4) {
65253             text = text.substr(0, text.length - 4) + "...";
65254             sprite.setAttributes({
65255                 text: text
65256             }, true);
65257             bbox = sprite.getBBox();
65258             if (bbox.width < desiredWidth) {
65259                 if (typeof center == 'number') {
65260                     sprite.setAttributes({
65261                         x: Math.floor(center - (bbox.width / 2))
65262                     }, true);
65263                 }
65264                 break;
65265             }
65266         }
65267         return true;
65268     },
65269
65270     /**
65271      * Updates the {@link #title} of this axis.
65272      * @param {String} title
65273      */
65274     setTitle: function(title) {
65275         this.title = title;
65276         this.drawLabel();
65277     },
65278
65279     // @private draws the title for the axis.
65280     drawTitle: function(maxWidth, maxHeight) {
65281         var me = this,
65282             position = me.position,
65283             surface = me.chart.surface,
65284             displaySprite = me.displaySprite,
65285             title = me.title,
65286             rotate = (position == 'left' || position == 'right'),
65287             x = me.x,
65288             y = me.y,
65289             base, bbox, pad;
65290
65291         if (displaySprite) {
65292             displaySprite.setAttributes({text: title}, true);
65293         } else {
65294             base = {
65295                 type: 'text',
65296                 x: 0,
65297                 y: 0,
65298                 text: title
65299             };
65300             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
65301             surface.renderItem(displaySprite);
65302         }
65303         bbox = displaySprite.getBBox();
65304         pad = me.dashSize + me.label.padding;
65305
65306         if (rotate) {
65307             y -= ((me.length / 2) - (bbox.height / 2));
65308             if (position == 'left') {
65309                 x -= (maxWidth + pad + (bbox.width / 2));
65310             }
65311             else {
65312                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
65313             }
65314             me.bbox.width += bbox.width + 10;
65315         }
65316         else {
65317             x += (me.length / 2) - (bbox.width * 0.5);
65318             if (position == 'top') {
65319                 y -= (maxHeight + pad + (bbox.height * 0.3));
65320             }
65321             else {
65322                 y += (maxHeight + pad + (bbox.height * 0.8));
65323             }
65324             me.bbox.height += bbox.height + 10;
65325         }
65326         displaySprite.setAttributes({
65327             translate: {
65328                 x: x,
65329                 y: y
65330             }
65331         }, true);
65332     }
65333 });
65334
65335 /**
65336  * @class Ext.chart.axis.Category
65337  * @extends Ext.chart.axis.Axis
65338  *
65339  * A type of axis that displays items in categories. This axis is generally used to
65340  * display categorical information like names of items, month names, quarters, etc.
65341  * but no quantitative values. For that other type of information `Number`
65342  * axis are more suitable.
65343  *
65344  * As with other axis you can set the position of the axis and its title. For example:
65345  *
65346  *     @example
65347  *     var store = Ext.create('Ext.data.JsonStore', {
65348  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65349  *         data: [
65350  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65351  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65352  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65353  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65354  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65355  *         ]
65356  *     });
65357  *
65358  *     Ext.create('Ext.chart.Chart', {
65359  *         renderTo: Ext.getBody(),
65360  *         width: 500,
65361  *         height: 300,
65362  *         store: store,
65363  *         axes: [{
65364  *             type: 'Numeric',
65365  *             grid: true,
65366  *             position: 'left',
65367  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65368  *             title: 'Sample Values',
65369  *             grid: {
65370  *                 odd: {
65371  *                     opacity: 1,
65372  *                     fill: '#ddd',
65373  *                     stroke: '#bbb',
65374  *                     'stroke-width': 1
65375  *                 }
65376  *             },
65377  *             minimum: 0,
65378  *             adjustMinimumByMajorUnit: 0
65379  *         }, {
65380  *             type: 'Category',
65381  *             position: 'bottom',
65382  *             fields: ['name'],
65383  *             title: 'Sample Metrics',
65384  *             grid: true,
65385  *             label: {
65386  *                 rotate: {
65387  *                     degrees: 315
65388  *                 }
65389  *             }
65390  *         }],
65391  *         series: [{
65392  *             type: 'area',
65393  *             highlight: false,
65394  *             axis: 'left',
65395  *             xField: 'name',
65396  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65397  *             style: {
65398  *                 opacity: 0.93
65399  *             }
65400  *         }]
65401  *     });
65402  *
65403  * In this example with set the category axis to the bottom of the surface, bound the axis to
65404  * the `name` property and set as title _Month of the Year_.
65405  */
65406 Ext.define('Ext.chart.axis.Category', {
65407
65408     /* Begin Definitions */
65409
65410     extend: 'Ext.chart.axis.Axis',
65411
65412     alternateClassName: 'Ext.chart.CategoryAxis',
65413
65414     alias: 'axis.category',
65415
65416     /* End Definitions */
65417
65418     /**
65419      * A list of category names to display along this axis.
65420      * @property {String} categoryNames
65421      */
65422     categoryNames: null,
65423
65424     /**
65425      * Indicates whether or not to calculate the number of categories (ticks and
65426      * labels) when there is not enough room to display all labels on the axis.
65427      * If set to true, the axis will determine the number of categories to plot.
65428      * If not, all categories will be plotted.
65429      *
65430      * @property calculateCategoryCount
65431      * @type Boolean
65432      */
65433     calculateCategoryCount: false,
65434
65435     // @private creates an array of labels to be used when rendering.
65436     setLabels: function() {
65437         var store = this.chart.store,
65438             fields = this.fields,
65439             ln = fields.length,
65440             i;
65441
65442         this.labels = [];
65443         store.each(function(record) {
65444             for (i = 0; i < ln; i++) {
65445                 this.labels.push(record.get(fields[i]));
65446             }
65447         }, this);
65448     },
65449
65450     // @private calculates labels positions and marker positions for rendering.
65451     applyData: function() {
65452         this.callParent();
65453         this.setLabels();
65454         var count = this.chart.store.getCount();
65455         return {
65456             from: 0,
65457             to: count,
65458             power: 1,
65459             step: 1,
65460             steps: count - 1
65461         };
65462     }
65463 });
65464
65465 /**
65466  * @class Ext.chart.axis.Gauge
65467  * @extends Ext.chart.axis.Abstract
65468  *
65469  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
65470  * displays numeric data from an interval defined by the `minimum`, `maximum` and
65471  * `step` configuration properties. The placement of the numeric data can be changed
65472  * by altering the `margin` option that is set to `10` by default.
65473  *
65474  * A possible configuration for this axis would look like:
65475  *
65476  *     axes: [{
65477  *         type: 'gauge',
65478  *         position: 'gauge',
65479  *         minimum: 0,
65480  *         maximum: 100,
65481  *         steps: 10,
65482  *         margin: 7
65483  *     }],
65484  */
65485 Ext.define('Ext.chart.axis.Gauge', {
65486
65487     /* Begin Definitions */
65488
65489     extend: 'Ext.chart.axis.Abstract',
65490
65491     /* End Definitions */
65492
65493     /**
65494      * @cfg {Number} minimum (required)
65495      * The minimum value of the interval to be displayed in the axis.
65496      */
65497
65498     /**
65499      * @cfg {Number} maximum (required)
65500      * The maximum value of the interval to be displayed in the axis.
65501      */
65502
65503     /**
65504      * @cfg {Number} steps (required)
65505      * The number of steps and tick marks to add to the interval.
65506      */
65507
65508     /**
65509      * @cfg {Number} [margin=10]
65510      * The offset positioning of the tick marks and labels in pixels.
65511      */
65512
65513     /**
65514      * @cfg {String} title
65515      * The title for the Axis.
65516      */
65517
65518     position: 'gauge',
65519
65520     alias: 'axis.gauge',
65521
65522     drawAxis: function(init) {
65523         var chart = this.chart,
65524             surface = chart.surface,
65525             bbox = chart.chartBBox,
65526             centerX = bbox.x + (bbox.width / 2),
65527             centerY = bbox.y + bbox.height,
65528             margin = this.margin || 10,
65529             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
65530             sprites = [], sprite,
65531             steps = this.steps,
65532             i, pi = Math.PI,
65533             cos = Math.cos,
65534             sin = Math.sin;
65535
65536         if (this.sprites && !chart.resizing) {
65537             this.drawLabel();
65538             return;
65539         }
65540
65541         if (this.margin >= 0) {
65542             if (!this.sprites) {
65543                 //draw circles
65544                 for (i = 0; i <= steps; i++) {
65545                     sprite = surface.add({
65546                         type: 'path',
65547                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
65548                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
65549                                     'L', centerX + rho * cos(i / steps * pi - pi),
65550                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
65551                         stroke: '#ccc'
65552                     });
65553                     sprite.setAttributes({
65554                         hidden: false
65555                     }, true);
65556                     sprites.push(sprite);
65557                 }
65558             } else {
65559                 sprites = this.sprites;
65560                 //draw circles
65561                 for (i = 0; i <= steps; i++) {
65562                     sprites[i].setAttributes({
65563                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
65564                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
65565                                'L', centerX + rho * cos(i / steps * pi - pi),
65566                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
65567                         stroke: '#ccc'
65568                     }, true);
65569                 }
65570             }
65571         }
65572         this.sprites = sprites;
65573         this.drawLabel();
65574         if (this.title) {
65575             this.drawTitle();
65576         }
65577     },
65578
65579     drawTitle: function() {
65580         var me = this,
65581             chart = me.chart,
65582             surface = chart.surface,
65583             bbox = chart.chartBBox,
65584             labelSprite = me.titleSprite,
65585             labelBBox;
65586
65587         if (!labelSprite) {
65588             me.titleSprite = labelSprite = surface.add({
65589                 type: 'text',
65590                 zIndex: 2
65591             });
65592         }
65593         labelSprite.setAttributes(Ext.apply({
65594             text: me.title
65595         }, me.label || {}), true);
65596         labelBBox = labelSprite.getBBox();
65597         labelSprite.setAttributes({
65598             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
65599             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
65600         }, true);
65601     },
65602
65603     /**
65604      * Updates the {@link #title} of this axis.
65605      * @param {String} title
65606      */
65607     setTitle: function(title) {
65608         this.title = title;
65609         this.drawTitle();
65610     },
65611
65612     drawLabel: function() {
65613         var chart = this.chart,
65614             surface = chart.surface,
65615             bbox = chart.chartBBox,
65616             centerX = bbox.x + (bbox.width / 2),
65617             centerY = bbox.y + bbox.height,
65618             margin = this.margin || 10,
65619             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
65620             round = Math.round,
65621             labelArray = [], label,
65622             maxValue = this.maximum || 0,
65623             steps = this.steps, i = 0,
65624             adjY,
65625             pi = Math.PI,
65626             cos = Math.cos,
65627             sin = Math.sin,
65628             labelConf = this.label,
65629             renderer = labelConf.renderer || function(v) { return v; };
65630
65631         if (!this.labelArray) {
65632             //draw scale
65633             for (i = 0; i <= steps; i++) {
65634                 // TODO Adjust for height of text / 2 instead
65635                 adjY = (i === 0 || i === steps) ? 7 : 0;
65636                 label = surface.add({
65637                     type: 'text',
65638                     text: renderer(round(i / steps * maxValue)),
65639                     x: centerX + rho * cos(i / steps * pi - pi),
65640                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
65641                     'text-anchor': 'middle',
65642                     'stroke-width': 0.2,
65643                     zIndex: 10,
65644                     stroke: '#333'
65645                 });
65646                 label.setAttributes({
65647                     hidden: false
65648                 }, true);
65649                 labelArray.push(label);
65650             }
65651         }
65652         else {
65653             labelArray = this.labelArray;
65654             //draw values
65655             for (i = 0; i <= steps; i++) {
65656                 // TODO Adjust for height of text / 2 instead
65657                 adjY = (i === 0 || i === steps) ? 7 : 0;
65658                 labelArray[i].setAttributes({
65659                     text: renderer(round(i / steps * maxValue)),
65660                     x: centerX + rho * cos(i / steps * pi - pi),
65661                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
65662                 }, true);
65663             }
65664         }
65665         this.labelArray = labelArray;
65666     }
65667 });
65668 /**
65669  * @class Ext.chart.axis.Numeric
65670  * @extends Ext.chart.axis.Axis
65671  *
65672  * An axis to handle numeric values. This axis is used for quantitative data as
65673  * opposed to the category axis. You can set mininum and maximum values to the
65674  * axis so that the values are bound to that. If no values are set, then the
65675  * scale will auto-adjust to the values.
65676  *
65677  *     @example
65678  *     var store = Ext.create('Ext.data.JsonStore', {
65679  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65680  *          data: [
65681  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65682  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65683  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65684  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65685  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65686  *          ]
65687  *     });
65688  *
65689  *     Ext.create('Ext.chart.Chart', {
65690  *         renderTo: Ext.getBody(),
65691  *         width: 500,
65692  *         height: 300,
65693  *         store: store,
65694  *         axes: [{
65695  *             type: 'Numeric',
65696  *             grid: true,
65697  *             position: 'left',
65698  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65699  *             title: 'Sample Values',
65700  *             grid: {
65701  *                 odd: {
65702  *                     opacity: 1,
65703  *                     fill: '#ddd',
65704  *                     stroke: '#bbb',
65705  *                     'stroke-width': 1
65706  *                 }
65707  *             },
65708  *             minimum: 0,
65709  *             adjustMinimumByMajorUnit: 0
65710  *         }, {
65711  *             type: 'Category',
65712  *             position: 'bottom',
65713  *             fields: ['name'],
65714  *             title: 'Sample Metrics',
65715  *             grid: true,
65716  *             label: {
65717  *                 rotate: {
65718  *                     degrees: 315
65719  *                 }
65720  *             }
65721  *         }],
65722  *         series: [{
65723  *             type: 'area',
65724  *             highlight: false,
65725  *             axis: 'left',
65726  *             xField: 'name',
65727  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65728  *             style: {
65729  *                 opacity: 0.93
65730  *             }
65731  *         }]
65732  *     });
65733  *
65734  * In this example we create an axis of Numeric type. We set a minimum value so that
65735  * even if all series have values greater than zero, the grid starts at zero. We bind
65736  * the axis onto the left part of the surface by setting `position` to `left`.
65737  * We bind three different store fields to this axis by setting `fields` to an array.
65738  * We set the title of the axis to _Number of Hits_ by using the `title` property.
65739  * We use a `grid` configuration to set odd background rows to a certain style and even rows
65740  * to be transparent/ignored.
65741  */
65742 Ext.define('Ext.chart.axis.Numeric', {
65743
65744     /* Begin Definitions */
65745
65746     extend: 'Ext.chart.axis.Axis',
65747
65748     alternateClassName: 'Ext.chart.NumericAxis',
65749
65750     /* End Definitions */
65751
65752     type: 'numeric',
65753
65754     alias: 'axis.numeric',
65755
65756     constructor: function(config) {
65757         var me = this,
65758             hasLabel = !!(config.label && config.label.renderer),
65759             label;
65760
65761         me.callParent([config]);
65762         label = me.label;
65763         if (me.roundToDecimal === false) {
65764             return;
65765         }
65766         if (!hasLabel) {
65767             label.renderer = function(v) {
65768                 return me.roundToDecimal(v, me.decimals);
65769             };
65770         }
65771     },
65772
65773     roundToDecimal: function(v, dec) {
65774         var val = Math.pow(10, dec || 0);
65775         return Math.floor(v * val) / val;
65776     },
65777
65778     /**
65779      * The minimum value drawn by the axis. If not set explicitly, the axis
65780      * minimum will be calculated automatically.
65781      *
65782      * @property {Number} minimum
65783      */
65784     minimum: NaN,
65785
65786     /**
65787      * The maximum value drawn by the axis. If not set explicitly, the axis
65788      * maximum will be calculated automatically.
65789      *
65790      * @property {Number} maximum
65791      */
65792     maximum: NaN,
65793
65794     /**
65795      * The number of decimals to round the value to.
65796      *
65797      * @property {Number} decimals
65798      */
65799     decimals: 2,
65800
65801     /**
65802      * The scaling algorithm to use on this axis. May be "linear" or
65803      * "logarithmic".  Currently only linear scale is implemented.
65804      *
65805      * @property {String} scale
65806      * @private
65807      */
65808     scale: "linear",
65809
65810     /**
65811      * Indicates the position of the axis relative to the chart
65812      *
65813      * @property {String} position
65814      */
65815     position: 'left',
65816
65817     /**
65818      * Indicates whether to extend maximum beyond data's maximum to the nearest
65819      * majorUnit.
65820      *
65821      * @property {Boolean} adjustMaximumByMajorUnit
65822      */
65823     adjustMaximumByMajorUnit: false,
65824
65825     /**
65826      * Indicates whether to extend the minimum beyond data's minimum to the
65827      * nearest majorUnit.
65828      *
65829      * @property {Boolean} adjustMinimumByMajorUnit
65830      */
65831     adjustMinimumByMajorUnit: false,
65832
65833     // @private apply data.
65834     applyData: function() {
65835         this.callParent();
65836         return this.calcEnds();
65837     }
65838 });
65839
65840 /**
65841  * @class Ext.chart.axis.Radial
65842  * @extends Ext.chart.axis.Abstract
65843  * @ignore
65844  */
65845 Ext.define('Ext.chart.axis.Radial', {
65846
65847     /* Begin Definitions */
65848
65849     extend: 'Ext.chart.axis.Abstract',
65850
65851     /* End Definitions */
65852
65853     position: 'radial',
65854
65855     alias: 'axis.radial',
65856
65857     drawAxis: function(init) {
65858         var chart = this.chart,
65859             surface = chart.surface,
65860             bbox = chart.chartBBox,
65861             store = chart.store,
65862             l = store.getCount(),
65863             centerX = bbox.x + (bbox.width / 2),
65864             centerY = bbox.y + (bbox.height / 2),
65865             rho = Math.min(bbox.width, bbox.height) /2,
65866             sprites = [], sprite,
65867             steps = this.steps,
65868             i, j, pi2 = Math.PI * 2,
65869             cos = Math.cos, sin = Math.sin;
65870
65871         if (this.sprites && !chart.resizing) {
65872             this.drawLabel();
65873             return;
65874         }
65875
65876         if (!this.sprites) {
65877             //draw circles
65878             for (i = 1; i <= steps; i++) {
65879                 sprite = surface.add({
65880                     type: 'circle',
65881                     x: centerX,
65882                     y: centerY,
65883                     radius: Math.max(rho * i / steps, 0),
65884                     stroke: '#ccc'
65885                 });
65886                 sprite.setAttributes({
65887                     hidden: false
65888                 }, true);
65889                 sprites.push(sprite);
65890             }
65891             //draw lines
65892             store.each(function(rec, i) {
65893                 sprite = surface.add({
65894                     type: 'path',
65895                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
65896                     stroke: '#ccc'
65897                 });
65898                 sprite.setAttributes({
65899                     hidden: false
65900                 }, true);
65901                 sprites.push(sprite);
65902             });
65903         } else {
65904             sprites = this.sprites;
65905             //draw circles
65906             for (i = 0; i < steps; i++) {
65907                 sprites[i].setAttributes({
65908                     x: centerX,
65909                     y: centerY,
65910                     radius: Math.max(rho * (i + 1) / steps, 0),
65911                     stroke: '#ccc'
65912                 }, true);
65913             }
65914             //draw lines
65915             store.each(function(rec, j) {
65916                 sprites[i + j].setAttributes({
65917                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
65918                     stroke: '#ccc'
65919                 }, true);
65920             });
65921         }
65922         this.sprites = sprites;
65923
65924         this.drawLabel();
65925     },
65926
65927     drawLabel: function() {
65928         var chart = this.chart,
65929             surface = chart.surface,
65930             bbox = chart.chartBBox,
65931             store = chart.store,
65932             centerX = bbox.x + (bbox.width / 2),
65933             centerY = bbox.y + (bbox.height / 2),
65934             rho = Math.min(bbox.width, bbox.height) /2,
65935             max = Math.max, round = Math.round,
65936             labelArray = [], label,
65937             fields = [], nfields,
65938             categories = [], xField,
65939             aggregate = !this.maximum,
65940             maxValue = this.maximum || 0,
65941             steps = this.steps, i = 0, j, dx, dy,
65942             pi2 = Math.PI * 2,
65943             cos = Math.cos, sin = Math.sin,
65944             display = this.label.display,
65945             draw = display !== 'none',
65946             margin = 10;
65947
65948         if (!draw) {
65949             return;
65950         }
65951
65952         //get all rendered fields
65953         chart.series.each(function(series) {
65954             fields.push(series.yField);
65955             xField = series.xField;
65956         });
65957         
65958         //get maxValue to interpolate
65959         store.each(function(record, i) {
65960             if (aggregate) {
65961                 for (i = 0, nfields = fields.length; i < nfields; i++) {
65962                     maxValue = max(+record.get(fields[i]), maxValue);
65963                 }
65964             }
65965             categories.push(record.get(xField));
65966         });
65967         if (!this.labelArray) {
65968             if (display != 'categories') {
65969                 //draw scale
65970                 for (i = 1; i <= steps; i++) {
65971                     label = surface.add({
65972                         type: 'text',
65973                         text: round(i / steps * maxValue),
65974                         x: centerX,
65975                         y: centerY - rho * i / steps,
65976                         'text-anchor': 'middle',
65977                         'stroke-width': 0.1,
65978                         stroke: '#333'
65979                     });
65980                     label.setAttributes({
65981                         hidden: false
65982                     }, true);
65983                     labelArray.push(label);
65984                 }
65985             }
65986             if (display != 'scale') {
65987                 //draw text
65988                 for (j = 0, steps = categories.length; j < steps; j++) {
65989                     dx = cos(j / steps * pi2) * (rho + margin);
65990                     dy = sin(j / steps * pi2) * (rho + margin);
65991                     label = surface.add({
65992                         type: 'text',
65993                         text: categories[j],
65994                         x: centerX + dx,
65995                         y: centerY + dy,
65996                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65997                     });
65998                     label.setAttributes({
65999                         hidden: false
66000                     }, true);
66001                     labelArray.push(label);
66002                 }
66003             }
66004         }
66005         else {
66006             labelArray = this.labelArray;
66007             if (display != 'categories') {
66008                 //draw values
66009                 for (i = 0; i < steps; i++) {
66010                     labelArray[i].setAttributes({
66011                         text: round((i + 1) / steps * maxValue),
66012                         x: centerX,
66013                         y: centerY - rho * (i + 1) / steps,
66014                         'text-anchor': 'middle',
66015                         'stroke-width': 0.1,
66016                         stroke: '#333'
66017                     }, true);
66018                 }
66019             }
66020             if (display != 'scale') {
66021                 //draw text
66022                 for (j = 0, steps = categories.length; j < steps; j++) {
66023                     dx = cos(j / steps * pi2) * (rho + margin);
66024                     dy = sin(j / steps * pi2) * (rho + margin);
66025                     if (labelArray[i + j]) {
66026                         labelArray[i + j].setAttributes({
66027                             type: 'text',
66028                             text: categories[j],
66029                             x: centerX + dx,
66030                             y: centerY + dy,
66031                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
66032                         }, true);
66033                     }
66034                 }
66035             }
66036         }
66037         this.labelArray = labelArray;
66038     }
66039 });
66040 /**
66041  * @author Ed Spencer
66042  *
66043  * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
66044  * but offers a set of methods used by both of those subclasses.
66045  * 
66046  * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
66047  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
66048  * AbstractStore is and is not.
66049  * 
66050  * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
66051  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
66052  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
66053  * 
66054  * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
66055  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
66056  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
66057  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
66058  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
66059  * 
66060  * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
66061  * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
66062  * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
66063  * {@link Ext.data.Store#filter filter} methods.
66064  */
66065 Ext.define('Ext.data.AbstractStore', {
66066     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
66067     
66068     mixins: {
66069         observable: 'Ext.util.Observable',
66070         sortable: 'Ext.util.Sortable'
66071     },
66072     
66073     statics: {
66074         create: function(store){
66075             if (!store.isStore) {
66076                 if (!store.type) {
66077                     store.type = 'store';
66078                 }
66079                 store = Ext.createByAlias('store.' + store.type, store);
66080             }
66081             return store;
66082         }    
66083     },
66084     
66085     remoteSort  : false,
66086     remoteFilter: false,
66087
66088     /**
66089      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
66090      * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
66091      * see {@link #setProxy} for details.
66092      */
66093
66094     /**
66095      * @cfg {Boolean/Object} autoLoad
66096      * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
66097      * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
66098      * Defaults to false.
66099      */
66100     autoLoad: false,
66101
66102     /**
66103      * @cfg {Boolean} autoSync
66104      * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
66105      */
66106     autoSync: false,
66107
66108     /**
66109      * @property {String} batchUpdateMode
66110      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
66111      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
66112      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
66113      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
66114      */
66115     batchUpdateMode: 'operation',
66116
66117     /**
66118      * @property {Boolean} filterOnLoad
66119      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
66120      * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
66121      */
66122     filterOnLoad: true,
66123
66124     /**
66125      * @property {Boolean} sortOnLoad
66126      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
66127      * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
66128      */
66129     sortOnLoad: true,
66130
66131     /**
66132      * @property {Boolean} implicitModel
66133      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
66134      * constructor instead of a model constructor or name.
66135      * @private
66136      */
66137     implicitModel: false,
66138
66139     /**
66140      * @property {String} defaultProxyType
66141      * The string type of the Proxy to create if none is specified. This defaults to creating a
66142      * {@link Ext.data.proxy.Memory memory proxy}.
66143      */
66144     defaultProxyType: 'memory',
66145
66146     /**
66147      * @property {Boolean} isDestroyed
66148      * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
66149      * as it will not function correctly any more.
66150      */
66151     isDestroyed: false,
66152
66153     isStore: true,
66154
66155     /**
66156      * @cfg {String} storeId
66157      * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
66158      * making it easy to reuse elsewhere. Defaults to undefined.
66159      */
66160     
66161     /**
66162      * @cfg {Object[]} fields
66163      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
66164      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
66165      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
66166      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
66167      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
66168      * config.
66169      */
66170
66171     /**
66172      * @cfg {String} model
66173      * Name of the {@link Ext.data.Model Model} associated with this store.
66174      * The string is used as an argument for {@link Ext.ModelManager#getModel}.
66175      */
66176
66177     sortRoot: 'data',
66178     
66179     //documented above
66180     constructor: function(config) {
66181         var me = this,
66182             filters;
66183         
66184         me.addEvents(
66185             /**
66186              * @event add
66187              * Fired when a Model instance has been added to this Store
66188              * @param {Ext.data.Store} store The store
66189              * @param {Ext.data.Model[]} records The Model instances that were added
66190              * @param {Number} index The index at which the instances were inserted
66191              */
66192             'add',
66193
66194             /**
66195              * @event remove
66196              * Fired when a Model instance has been removed from this Store
66197              * @param {Ext.data.Store} store The Store object
66198              * @param {Ext.data.Model} record The record that was removed
66199              * @param {Number} index The index of the record that was removed
66200              */
66201             'remove',
66202             
66203             /**
66204              * @event update
66205              * Fires when a Model instance has been updated
66206              * @param {Ext.data.Store} this
66207              * @param {Ext.data.Model} record The Model instance that was updated
66208              * @param {String} operation The update operation being performed. Value may be one of:
66209              *
66210              *     Ext.data.Model.EDIT
66211              *     Ext.data.Model.REJECT
66212              *     Ext.data.Model.COMMIT
66213              */
66214             'update',
66215
66216             /**
66217              * @event datachanged
66218              * Fires whenever the records in the Store have changed in some way - this could include adding or removing
66219              * records, or updating the data in existing records
66220              * @param {Ext.data.Store} this The data store
66221              */
66222             'datachanged',
66223
66224             /**
66225              * @event beforeload
66226              * Fires before a request is made for a new data object. If the beforeload handler returns false the load
66227              * action will be canceled.
66228              * @param {Ext.data.Store} store This Store
66229              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
66230              * load the Store
66231              */
66232             'beforeload',
66233
66234             /**
66235              * @event load
66236              * Fires whenever the store reads data from a remote data source.
66237              * @param {Ext.data.Store} this
66238              * @param {Ext.data.Model[]} records An array of records
66239              * @param {Boolean} successful True if the operation was successful.
66240              */
66241             'load',
66242             
66243             /**
66244              * @event write
66245              * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
66246              * @param {Ext.data.Store} store This Store
66247              * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
66248              * the write
66249              */
66250             'write',
66251
66252             /**
66253              * @event beforesync
66254              * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
66255              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
66256              */
66257             'beforesync',
66258             /**
66259              * @event clear
66260              * Fired after the {@link #removeAll} method is called.
66261              * @param {Ext.data.Store} this
66262              */
66263             'clear'
66264         );
66265         
66266         Ext.apply(me, config);
66267         // don't use *config* anymore from here on... use *me* instead...
66268
66269         /**
66270          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
66271          * at which point this is cleared.
66272          * @private
66273          * @property {Ext.data.Model[]} removed
66274          */
66275         me.removed = [];
66276
66277         me.mixins.observable.constructor.apply(me, arguments);
66278         me.model = Ext.ModelManager.getModel(me.model);
66279
66280         /**
66281          * @property {Object} modelDefaults
66282          * @private
66283          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
66284          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
66285          * for examples. This should not need to be used by application developers.
66286          */
66287         Ext.applyIf(me, {
66288             modelDefaults: {}
66289         });
66290
66291         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
66292         if (!me.model && me.fields) {
66293             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
66294                 extend: 'Ext.data.Model',
66295                 fields: me.fields,
66296                 proxy: me.proxy || me.defaultProxyType
66297             });
66298
66299             delete me.fields;
66300
66301             me.implicitModel = true;
66302         }
66303         
66304         if (!me.model) {
66305             if (Ext.isDefined(Ext.global.console)) {
66306                 Ext.global.console.warn('Store defined with no model. You may have mistyped the model name.');
66307             }
66308         }
66309
66310         //ensures that the Proxy is instantiated correctly
66311         me.setProxy(me.proxy || me.model.getProxy());
66312
66313         if (me.id && !me.storeId) {
66314             me.storeId = me.id;
66315             delete me.id;
66316         }
66317
66318         if (me.storeId) {
66319             Ext.data.StoreManager.register(me);
66320         }
66321         
66322         me.mixins.sortable.initSortable.call(me);        
66323         
66324         /**
66325          * @property {Ext.util.MixedCollection} filters
66326          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
66327          */
66328         filters = me.decodeFilters(me.filters);
66329         me.filters = Ext.create('Ext.util.MixedCollection');
66330         me.filters.addAll(filters);
66331     },
66332
66333     /**
66334      * Sets the Store's Proxy by string, config object or Proxy instance
66335      * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
66336      * or an Ext.data.proxy.Proxy instance
66337      * @return {Ext.data.proxy.Proxy} The attached Proxy object
66338      */
66339     setProxy: function(proxy) {
66340         var me = this;
66341         
66342         if (proxy instanceof Ext.data.proxy.Proxy) {
66343             proxy.setModel(me.model);
66344         } else {
66345             if (Ext.isString(proxy)) {
66346                 proxy = {
66347                     type: proxy    
66348                 };
66349             }
66350             Ext.applyIf(proxy, {
66351                 model: me.model
66352             });
66353             
66354             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
66355         }
66356         
66357         me.proxy = proxy;
66358         
66359         return me.proxy;
66360     },
66361
66362     /**
66363      * Returns the proxy currently attached to this proxy instance
66364      * @return {Ext.data.proxy.Proxy} The Proxy instance
66365      */
66366     getProxy: function() {
66367         return this.proxy;
66368     },
66369
66370     //saves any phantom records
66371     create: function(data, options) {
66372         var me = this,
66373             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
66374             operation;
66375         
66376         options = options || {};
66377
66378         Ext.applyIf(options, {
66379             action : 'create',
66380             records: [instance]
66381         });
66382
66383         operation = Ext.create('Ext.data.Operation', options);
66384
66385         me.proxy.create(operation, me.onProxyWrite, me);
66386         
66387         return instance;
66388     },
66389
66390     read: function() {
66391         return this.load.apply(this, arguments);
66392     },
66393
66394     onProxyRead: Ext.emptyFn,
66395
66396     update: function(options) {
66397         var me = this,
66398             operation;
66399         options = options || {};
66400
66401         Ext.applyIf(options, {
66402             action : 'update',
66403             records: me.getUpdatedRecords()
66404         });
66405
66406         operation = Ext.create('Ext.data.Operation', options);
66407
66408         return me.proxy.update(operation, me.onProxyWrite, me);
66409     },
66410
66411     /**
66412      * @private
66413      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
66414      * the updates provided by the Proxy
66415      */
66416     onProxyWrite: function(operation) {
66417         var me = this,
66418             success = operation.wasSuccessful(),
66419             records = operation.getRecords();
66420
66421         switch (operation.action) {
66422             case 'create':
66423                 me.onCreateRecords(records, operation, success);
66424                 break;
66425             case 'update':
66426                 me.onUpdateRecords(records, operation, success);
66427                 break;
66428             case 'destroy':
66429                 me.onDestroyRecords(records, operation, success);
66430                 break;
66431         }
66432
66433         if (success) {
66434             me.fireEvent('write', me, operation);
66435             me.fireEvent('datachanged', me);
66436         }
66437         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
66438         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
66439     },
66440
66441
66442     //tells the attached proxy to destroy the given records
66443     destroy: function(options) {
66444         var me = this,
66445             operation;
66446             
66447         options = options || {};
66448
66449         Ext.applyIf(options, {
66450             action : 'destroy',
66451             records: me.getRemovedRecords()
66452         });
66453
66454         operation = Ext.create('Ext.data.Operation', options);
66455
66456         return me.proxy.destroy(operation, me.onProxyWrite, me);
66457     },
66458
66459     /**
66460      * @private
66461      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
66462      * to onProxyWrite.
66463      */
66464     onBatchOperationComplete: function(batch, operation) {
66465         return this.onProxyWrite(operation);
66466     },
66467
66468     /**
66469      * @private
66470      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
66471      * and updates the Store's internal data MixedCollection.
66472      */
66473     onBatchComplete: function(batch, operation) {
66474         var me = this,
66475             operations = batch.operations,
66476             length = operations.length,
66477             i;
66478
66479         me.suspendEvents();
66480
66481         for (i = 0; i < length; i++) {
66482             me.onProxyWrite(operations[i]);
66483         }
66484
66485         me.resumeEvents();
66486
66487         me.fireEvent('datachanged', me);
66488     },
66489
66490     onBatchException: function(batch, operation) {
66491         // //decide what to do... could continue with the next operation
66492         // batch.start();
66493         //
66494         // //or retry the last operation
66495         // batch.retry();
66496     },
66497
66498     /**
66499      * @private
66500      * Filter function for new records.
66501      */
66502     filterNew: function(item) {
66503         // only want phantom records that are valid
66504         return item.phantom === true && item.isValid();
66505     },
66506
66507     /**
66508      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
66509      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
66510      * @return {Ext.data.Model[]} The Model instances
66511      */
66512     getNewRecords: function() {
66513         return [];
66514     },
66515
66516     /**
66517      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
66518      * @return {Ext.data.Model[]} The updated Model instances
66519      */
66520     getUpdatedRecords: function() {
66521         return [];
66522     },
66523
66524     /**
66525      * @private
66526      * Filter function for updated records.
66527      */
66528     filterUpdated: function(item) {
66529         // only want dirty records, not phantoms that are valid
66530         return item.dirty === true && item.phantom !== true && item.isValid();
66531     },
66532
66533     /**
66534      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
66535      * @return {Ext.data.Model[]} The removed Model instances
66536      */
66537     getRemovedRecords: function() {
66538         return this.removed;
66539     },
66540
66541     filter: function(filters, value) {
66542
66543     },
66544
66545     /**
66546      * @private
66547      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
66548      * @param {Object[]} filters The filters array
66549      * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
66550      */
66551     decodeFilters: function(filters) {
66552         if (!Ext.isArray(filters)) {
66553             if (filters === undefined) {
66554                 filters = [];
66555             } else {
66556                 filters = [filters];
66557             }
66558         }
66559
66560         var length = filters.length,
66561             Filter = Ext.util.Filter,
66562             config, i;
66563
66564         for (i = 0; i < length; i++) {
66565             config = filters[i];
66566
66567             if (!(config instanceof Filter)) {
66568                 Ext.apply(config, {
66569                     root: 'data'
66570                 });
66571
66572                 //support for 3.x style filters where a function can be defined as 'fn'
66573                 if (config.fn) {
66574                     config.filterFn = config.fn;
66575                 }
66576
66577                 //support a function to be passed as a filter definition
66578                 if (typeof config == 'function') {
66579                     config = {
66580                         filterFn: config
66581                     };
66582                 }
66583
66584                 filters[i] = new Filter(config);
66585             }
66586         }
66587
66588         return filters;
66589     },
66590
66591     clearFilter: function(supressEvent) {
66592
66593     },
66594
66595     isFiltered: function() {
66596
66597     },
66598
66599     filterBy: function(fn, scope) {
66600
66601     },
66602     
66603     /**
66604      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
66605      * and deleted records in the store, updating the Store's internal representation of the records
66606      * as each operation completes.
66607      */
66608     sync: function() {
66609         var me        = this,
66610             options   = {},
66611             toCreate  = me.getNewRecords(),
66612             toUpdate  = me.getUpdatedRecords(),
66613             toDestroy = me.getRemovedRecords(),
66614             needsSync = false;
66615
66616         if (toCreate.length > 0) {
66617             options.create = toCreate;
66618             needsSync = true;
66619         }
66620
66621         if (toUpdate.length > 0) {
66622             options.update = toUpdate;
66623             needsSync = true;
66624         }
66625
66626         if (toDestroy.length > 0) {
66627             options.destroy = toDestroy;
66628             needsSync = true;
66629         }
66630
66631         if (needsSync && me.fireEvent('beforesync', options) !== false) {
66632             me.proxy.batch(options, me.getBatchListeners());
66633         }
66634     },
66635
66636
66637     /**
66638      * @private
66639      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
66640      * This is broken out into a separate function to allow for customisation of the listeners
66641      * @return {Object} The listeners object
66642      */
66643     getBatchListeners: function() {
66644         var me = this,
66645             listeners = {
66646                 scope: me,
66647                 exception: me.onBatchException
66648             };
66649
66650         if (me.batchUpdateMode == 'operation') {
66651             listeners.operationcomplete = me.onBatchOperationComplete;
66652         } else {
66653             listeners.complete = me.onBatchComplete;
66654         }
66655
66656         return listeners;
66657     },
66658
66659     //deprecated, will be removed in 5.0
66660     save: function() {
66661         return this.sync.apply(this, arguments);
66662     },
66663
66664     /**
66665      * Loads the Store using its configured {@link #proxy}.
66666      * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
66667      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
66668      */
66669     load: function(options) {
66670         var me = this,
66671             operation;
66672
66673         options = options || {};
66674
66675         Ext.applyIf(options, {
66676             action : 'read',
66677             filters: me.filters.items,
66678             sorters: me.getSorters()
66679         });
66680         
66681         operation = Ext.create('Ext.data.Operation', options);
66682
66683         if (me.fireEvent('beforeload', me, operation) !== false) {
66684             me.loading = true;
66685             me.proxy.read(operation, me.onProxyLoad, me);
66686         }
66687         
66688         return me;
66689     },
66690
66691     /**
66692      * @private
66693      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66694      * @param {Ext.data.Model} record The model instance that was edited
66695      */
66696     afterEdit : function(record) {
66697         var me = this;
66698         
66699         if (me.autoSync) {
66700             me.sync();
66701         }
66702         
66703         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
66704     },
66705
66706     /**
66707      * @private
66708      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
66709      * @param {Ext.data.Model} record The model instance that was edited
66710      */
66711     afterReject : function(record) {
66712         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
66713     },
66714
66715     /**
66716      * @private
66717      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66718      * @param {Ext.data.Model} record The model instance that was edited
66719      */
66720     afterCommit : function(record) {
66721         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
66722     },
66723
66724     clearData: Ext.emptyFn,
66725
66726     destroyStore: function() {
66727         var me = this;
66728         
66729         if (!me.isDestroyed) {
66730             if (me.storeId) {
66731                 Ext.data.StoreManager.unregister(me);
66732             }
66733             me.clearData();
66734             me.data = null;
66735             me.tree = null;
66736             // Ext.destroy(this.proxy);
66737             me.reader = me.writer = null;
66738             me.clearListeners();
66739             me.isDestroyed = true;
66740
66741             if (me.implicitModel) {
66742                 Ext.destroy(me.model);
66743             }
66744         }
66745     },
66746     
66747     doSort: function(sorterFn) {
66748         var me = this;
66749         if (me.remoteSort) {
66750             //the load function will pick up the new sorters and request the sorted data from the proxy
66751             me.load();
66752         } else {
66753             me.data.sortBy(sorterFn);
66754             me.fireEvent('datachanged', me);
66755         }
66756     },
66757
66758     getCount: Ext.emptyFn,
66759
66760     getById: Ext.emptyFn,
66761     
66762     /**
66763      * Removes all records from the store. This method does a "fast remove",
66764      * individual remove events are not called. The {@link #clear} event is
66765      * fired upon completion.
66766      * @method
66767      */
66768     removeAll: Ext.emptyFn,
66769     // individual substores should implement a "fast" remove
66770     // and fire a clear event afterwards
66771
66772     /**
66773      * Returns true if the Store is currently performing a load operation
66774      * @return {Boolean} True if the Store is currently loading
66775      */
66776     isLoading: function() {
66777         return !!this.loading;
66778      }
66779 });
66780
66781 /**
66782  * @class Ext.util.Grouper
66783  * @extends Ext.util.Sorter
66784
66785 Represents a single grouper that can be applied to a Store. The grouper works
66786 in the same fashion as the {@link Ext.util.Sorter}.
66787
66788  * @markdown
66789  */
66790  
66791 Ext.define('Ext.util.Grouper', {
66792
66793     /* Begin Definitions */
66794
66795     extend: 'Ext.util.Sorter',
66796
66797     /* End Definitions */
66798
66799     /**
66800      * Returns the value for grouping to be used.
66801      * @param {Ext.data.Model} instance The Model instance
66802      * @return {String} The group string for this model
66803      */
66804     getGroupString: function(instance) {
66805         return instance.get(this.property);
66806     }
66807 });
66808 /**
66809  * @author Ed Spencer
66810  * @class Ext.data.Store
66811  * @extends Ext.data.AbstractStore
66812  *
66813  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
66814  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
66815  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
66816  *
66817  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
66818  *
66819 <pre><code>
66820 // Set up a {@link Ext.data.Model model} to use in our Store
66821 Ext.define('User', {
66822     extend: 'Ext.data.Model',
66823     fields: [
66824         {name: 'firstName', type: 'string'},
66825         {name: 'lastName',  type: 'string'},
66826         {name: 'age',       type: 'int'},
66827         {name: 'eyeColor',  type: 'string'}
66828     ]
66829 });
66830
66831 var myStore = Ext.create('Ext.data.Store', {
66832     model: 'User',
66833     proxy: {
66834         type: 'ajax',
66835         url : '/users.json',
66836         reader: {
66837             type: 'json',
66838             root: 'users'
66839         }
66840     },
66841     autoLoad: true
66842 });
66843 </code></pre>
66844
66845  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
66846  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
66847  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
66848  *
66849  * <p><u>Inline data</u></p>
66850  *
66851  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
66852  * into Model instances:</p>
66853  *
66854 <pre><code>
66855 Ext.create('Ext.data.Store', {
66856     model: 'User',
66857     data : [
66858         {firstName: 'Ed',    lastName: 'Spencer'},
66859         {firstName: 'Tommy', lastName: 'Maintz'},
66860         {firstName: 'Aaron', lastName: 'Conran'},
66861         {firstName: 'Jamie', lastName: 'Avins'}
66862     ]
66863 });
66864 </code></pre>
66865  *
66866  * <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
66867  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
66868  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
66869  *
66870  * <p>Additional data can also be loaded locally using {@link #add}.</p>
66871  *
66872  * <p><u>Loading Nested Data</u></p>
66873  *
66874  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
66875  * 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
66876  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
66877  * docs for a full explanation:</p>
66878  *
66879 <pre><code>
66880 var store = Ext.create('Ext.data.Store', {
66881     autoLoad: true,
66882     model: "User",
66883     proxy: {
66884         type: 'ajax',
66885         url : 'users.json',
66886         reader: {
66887             type: 'json',
66888             root: 'users'
66889         }
66890     }
66891 });
66892 </code></pre>
66893  *
66894  * <p>Which would consume a response like this:</p>
66895  *
66896 <pre><code>
66897 {
66898     "users": [
66899         {
66900             "id": 1,
66901             "name": "Ed",
66902             "orders": [
66903                 {
66904                     "id": 10,
66905                     "total": 10.76,
66906                     "status": "invoiced"
66907                 },
66908                 {
66909                     "id": 11,
66910                     "total": 13.45,
66911                     "status": "shipped"
66912                 }
66913             ]
66914         }
66915     ]
66916 }
66917 </code></pre>
66918  *
66919  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
66920  *
66921  * <p><u>Filtering and Sorting</u></p>
66922  *
66923  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
66924  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
66925  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
66926  *
66927 <pre><code>
66928 var store = Ext.create('Ext.data.Store', {
66929     model: 'User',
66930     sorters: [
66931         {
66932             property : 'age',
66933             direction: 'DESC'
66934         },
66935         {
66936             property : 'firstName',
66937             direction: 'ASC'
66938         }
66939     ],
66940
66941     filters: [
66942         {
66943             property: 'firstName',
66944             value   : /Ed/
66945         }
66946     ]
66947 });
66948 </code></pre>
66949  *
66950  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
66951  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
66952  * perform these operations instead.</p>
66953  *
66954  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
66955  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
66956  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
66957  *
66958 <pre><code>
66959 store.filter('eyeColor', 'Brown');
66960 </code></pre>
66961  *
66962  * <p>Change the sorting at any time by calling {@link #sort}:</p>
66963  *
66964 <pre><code>
66965 store.sort('height', 'ASC');
66966 </code></pre>
66967  *
66968  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
66969  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
66970  * to the MixedCollection:</p>
66971  *
66972 <pre><code>
66973 store.sorters.add(new Ext.util.Sorter({
66974     property : 'shoeSize',
66975     direction: 'ASC'
66976 }));
66977
66978 store.sort();
66979 </code></pre>
66980  *
66981  * <p><u>Registering with StoreManager</u></p>
66982  *
66983  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
66984  * This makes it easy to reuse the same store in multiple views:</p>
66985  *
66986  <pre><code>
66987 //this store can be used several times
66988 Ext.create('Ext.data.Store', {
66989     model: 'User',
66990     storeId: 'usersStore'
66991 });
66992
66993 new Ext.List({
66994     store: 'usersStore',
66995
66996     //other config goes here
66997 });
66998
66999 new Ext.view.View({
67000     store: 'usersStore',
67001
67002     //other config goes here
67003 });
67004 </code></pre>
67005  *
67006  * <p><u>Further Reading</u></p>
67007  *
67008  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
67009  * pieces and how they fit together, see:</p>
67010  *
67011  * <ul style="list-style-type: disc; padding-left: 25px">
67012  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
67013  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
67014  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
67015  * </ul>
67016  *
67017  */
67018 Ext.define('Ext.data.Store', {
67019     extend: 'Ext.data.AbstractStore',
67020
67021     alias: 'store.store',
67022
67023     requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
67024     uses: ['Ext.data.proxy.Memory'],
67025
67026     /**
67027      * @cfg {Boolean} remoteSort
67028      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
67029      */
67030     remoteSort: false,
67031
67032     /**
67033      * @cfg {Boolean} remoteFilter
67034      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
67035      */
67036     remoteFilter: false,
67037
67038     /**
67039      * @cfg {Boolean} remoteGroup
67040      * True if the grouping should apply on the server side, false if it is local only.  If the
67041      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
67042      * helper, automatically sending the grouping information to the server.
67043      */
67044     remoteGroup : false,
67045
67046     /**
67047      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
67048      * object or a Proxy instance - see {@link #setProxy} for details.
67049      */
67050
67051     /**
67052      * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
67053      */
67054
67055     /**
67056      * @property {String} groupField
67057      * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
67058      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
67059      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
67060      */
67061     groupField: undefined,
67062
67063     /**
67064      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
67065      * @property groupDir
67066      * @type String
67067      */
67068     groupDir: "ASC",
67069
67070     /**
67071      * @cfg {Number} pageSize
67072      * The number of records considered to form a 'page'. This is used to power the built-in
67073      * paging using the nextPage and previousPage functions. Defaults to 25.
67074      */
67075     pageSize: 25,
67076
67077     /**
67078      * The page that the Store has most recently loaded (see {@link #loadPage})
67079      * @property currentPage
67080      * @type Number
67081      */
67082     currentPage: 1,
67083
67084     /**
67085      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
67086      * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
67087      * large data sets to be loaded one page at a time but rendered all together.
67088      */
67089     clearOnPageLoad: true,
67090
67091     /**
67092      * @property {Boolean} loading
67093      * True if the Store is currently loading via its Proxy
67094      * @private
67095      */
67096     loading: false,
67097
67098     /**
67099      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
67100      * causing the sorters to be reapplied after filtering. Defaults to true
67101      */
67102     sortOnFilter: true,
67103
67104     /**
67105      * @cfg {Boolean} buffered
67106      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
67107      * tell the store to pre-fetch records ahead of a time.
67108      */
67109     buffered: false,
67110
67111     /**
67112      * @cfg {Number} purgePageCount
67113      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
67114      * This option is only relevant when the {@link #buffered} option is set to true.
67115      */
67116     purgePageCount: 5,
67117
67118     isStore: true,
67119
67120     onClassExtended: function(cls, data) {
67121         var model = data.model;
67122
67123         if (typeof model == 'string') {
67124             var onBeforeClassCreated = data.onBeforeClassCreated;
67125
67126             data.onBeforeClassCreated = function(cls, data) {
67127                 var me = this;
67128
67129                 Ext.require(model, function() {
67130                     onBeforeClassCreated.call(me, cls, data);
67131                 });
67132             };
67133         }
67134     },
67135
67136     /**
67137      * Creates the store.
67138      * @param {Object} config (optional) Config object
67139      */
67140     constructor: function(config) {
67141         // Clone the config so we don't modify the original config object
67142         config = Ext.Object.merge({}, config);
67143
67144         var me = this,
67145             groupers = config.groupers || me.groupers,
67146             groupField = config.groupField || me.groupField,
67147             proxy,
67148             data;
67149
67150         if (config.buffered || me.buffered) {
67151             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
67152                 return record.index;
67153             });
67154             me.pendingRequests = [];
67155             me.pagesRequested = [];
67156
67157             me.sortOnLoad = false;
67158             me.filterOnLoad = false;
67159         }
67160
67161         me.addEvents(
67162             /**
67163              * @event beforeprefetch
67164              * Fires before a prefetch occurs. Return false to cancel.
67165              * @param {Ext.data.Store} this
67166              * @param {Ext.data.Operation} operation The associated operation
67167              */
67168             'beforeprefetch',
67169             /**
67170              * @event groupchange
67171              * Fired whenever the grouping in the grid changes
67172              * @param {Ext.data.Store} store The store
67173              * @param {Ext.util.Grouper[]} groupers The array of grouper objects
67174              */
67175             'groupchange',
67176             /**
67177              * @event load
67178              * Fires whenever records have been prefetched
67179              * @param {Ext.data.Store} this
67180              * @param {Ext.util.Grouper[]} records An array of records
67181              * @param {Boolean} successful True if the operation was successful.
67182              * @param {Ext.data.Operation} operation The associated operation
67183              */
67184             'prefetch'
67185         );
67186         data = config.data || me.data;
67187
67188         /**
67189          * The MixedCollection that holds this store's local cache of records
67190          * @property data
67191          * @type Ext.util.MixedCollection
67192          */
67193         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
67194             return record.internalId;
67195         });
67196
67197         if (data) {
67198             me.inlineData = data;
67199             delete config.data;
67200         }
67201
67202         if (!groupers && groupField) {
67203             groupers = [{
67204                 property : groupField,
67205                 direction: config.groupDir || me.groupDir
67206             }];
67207         }
67208         delete config.groupers;
67209
67210         /**
67211          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
67212          * @property groupers
67213          * @type Ext.util.MixedCollection
67214          */
67215         me.groupers = Ext.create('Ext.util.MixedCollection');
67216         me.groupers.addAll(me.decodeGroupers(groupers));
67217
67218         this.callParent([config]);
67219         // don't use *config* anymore from here on... use *me* instead...
67220
67221         if (me.groupers.items.length) {
67222             me.sort(me.groupers.items, 'prepend', false);
67223         }
67224
67225         proxy = me.proxy;
67226         data = me.inlineData;
67227
67228         if (data) {
67229             if (proxy instanceof Ext.data.proxy.Memory) {
67230                 proxy.data = data;
67231                 me.read();
67232             } else {
67233                 me.add.apply(me, data);
67234             }
67235
67236             me.sort();
67237             delete me.inlineData;
67238         } else if (me.autoLoad) {
67239             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
67240             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
67241             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
67242         }
67243     },
67244
67245     onBeforeSort: function() {
67246         var groupers = this.groupers;
67247         if (groupers.getCount() > 0) {
67248             this.sort(groupers.items, 'prepend', false);
67249         }
67250     },
67251
67252     /**
67253      * @private
67254      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
67255      * @param {Object[]} groupers The groupers array
67256      * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
67257      */
67258     decodeGroupers: function(groupers) {
67259         if (!Ext.isArray(groupers)) {
67260             if (groupers === undefined) {
67261                 groupers = [];
67262             } else {
67263                 groupers = [groupers];
67264             }
67265         }
67266
67267         var length  = groupers.length,
67268             Grouper = Ext.util.Grouper,
67269             config, i;
67270
67271         for (i = 0; i < length; i++) {
67272             config = groupers[i];
67273
67274             if (!(config instanceof Grouper)) {
67275                 if (Ext.isString(config)) {
67276                     config = {
67277                         property: config
67278                     };
67279                 }
67280
67281                 Ext.applyIf(config, {
67282                     root     : 'data',
67283                     direction: "ASC"
67284                 });
67285
67286                 //support for 3.x style sorters where a function can be defined as 'fn'
67287                 if (config.fn) {
67288                     config.sorterFn = config.fn;
67289                 }
67290
67291                 //support a function to be passed as a sorter definition
67292                 if (typeof config == 'function') {
67293                     config = {
67294                         sorterFn: config
67295                     };
67296                 }
67297
67298                 groupers[i] = new Grouper(config);
67299             }
67300         }
67301
67302         return groupers;
67303     },
67304
67305     /**
67306      * Group data in the store
67307      * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
67308      * or an Array of grouper configurations.
67309      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
67310      */
67311     group: function(groupers, direction) {
67312         var me = this,
67313             hasNew = false,
67314             grouper,
67315             newGroupers;
67316
67317         if (Ext.isArray(groupers)) {
67318             newGroupers = groupers;
67319         } else if (Ext.isObject(groupers)) {
67320             newGroupers = [groupers];
67321         } else if (Ext.isString(groupers)) {
67322             grouper = me.groupers.get(groupers);
67323
67324             if (!grouper) {
67325                 grouper = {
67326                     property : groupers,
67327                     direction: direction
67328                 };
67329                 newGroupers = [grouper];
67330             } else if (direction === undefined) {
67331                 grouper.toggle();
67332             } else {
67333                 grouper.setDirection(direction);
67334             }
67335         }
67336
67337         if (newGroupers && newGroupers.length) {
67338             hasNew = true;
67339             newGroupers = me.decodeGroupers(newGroupers);
67340             me.groupers.clear();
67341             me.groupers.addAll(newGroupers);
67342         }
67343
67344         if (me.remoteGroup) {
67345             me.load({
67346                 scope: me,
67347                 callback: me.fireGroupChange
67348             });
67349         } else {
67350             // need to explicitly force a sort if we have groupers
67351             me.sort(null, null, null, hasNew);
67352             me.fireGroupChange();
67353         }
67354     },
67355
67356     /**
67357      * Clear any groupers in the store
67358      */
67359     clearGrouping: function(){
67360         var me = this;
67361         // Clear any groupers we pushed on to the sorters
67362         me.groupers.each(function(grouper){
67363             me.sorters.remove(grouper);
67364         });
67365         me.groupers.clear();
67366         if (me.remoteGroup) {
67367             me.load({
67368                 scope: me,
67369                 callback: me.fireGroupChange
67370             });
67371         } else {
67372             me.sort();
67373             me.fireEvent('groupchange', me, me.groupers);
67374         }
67375     },
67376
67377     /**
67378      * Checks if the store is currently grouped
67379      * @return {Boolean} True if the store is grouped.
67380      */
67381     isGrouped: function() {
67382         return this.groupers.getCount() > 0;
67383     },
67384
67385     /**
67386      * Fires the groupchange event. Abstracted out so we can use it
67387      * as a callback
67388      * @private
67389      */
67390     fireGroupChange: function(){
67391         this.fireEvent('groupchange', this, this.groupers);
67392     },
67393
67394     /**
67395      * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
67396      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
67397 <pre><code>
67398 var myStore = Ext.create('Ext.data.Store', {
67399     groupField: 'color',
67400     groupDir  : 'DESC'
67401 });
67402
67403 myStore.getGroups(); //returns:
67404 [
67405     {
67406         name: 'yellow',
67407         children: [
67408             //all records where the color field is 'yellow'
67409         ]
67410     },
67411     {
67412         name: 'red',
67413         children: [
67414             //all records where the color field is 'red'
67415         ]
67416     }
67417 ]
67418 </code></pre>
67419      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
67420      * @return {Object/Object[]} The grouped data
67421      */
67422     getGroups: function(requestGroupString) {
67423         var records = this.data.items,
67424             length = records.length,
67425             groups = [],
67426             pointers = {},
67427             record,
67428             groupStr,
67429             group,
67430             i;
67431
67432         for (i = 0; i < length; i++) {
67433             record = records[i];
67434             groupStr = this.getGroupString(record);
67435             group = pointers[groupStr];
67436
67437             if (group === undefined) {
67438                 group = {
67439                     name: groupStr,
67440                     children: []
67441                 };
67442
67443                 groups.push(group);
67444                 pointers[groupStr] = group;
67445             }
67446
67447             group.children.push(record);
67448         }
67449
67450         return requestGroupString ? pointers[requestGroupString] : groups;
67451     },
67452
67453     /**
67454      * @private
67455      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
67456      * matching a certain group.
67457      */
67458     getGroupsForGrouper: function(records, grouper) {
67459         var length = records.length,
67460             groups = [],
67461             oldValue,
67462             newValue,
67463             record,
67464             group,
67465             i;
67466
67467         for (i = 0; i < length; i++) {
67468             record = records[i];
67469             newValue = grouper.getGroupString(record);
67470
67471             if (newValue !== oldValue) {
67472                 group = {
67473                     name: newValue,
67474                     grouper: grouper,
67475                     records: []
67476                 };
67477                 groups.push(group);
67478             }
67479
67480             group.records.push(record);
67481
67482             oldValue = newValue;
67483         }
67484
67485         return groups;
67486     },
67487
67488     /**
67489      * @private
67490      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
67491      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
67492      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
67493      * @param {Ext.data.Model[]} records The set or subset of records to group
67494      * @param {Number} grouperIndex The grouper index to retrieve
67495      * @return {Object[]} The grouped records
67496      */
67497     getGroupsForGrouperIndex: function(records, grouperIndex) {
67498         var me = this,
67499             groupers = me.groupers,
67500             grouper = groupers.getAt(grouperIndex),
67501             groups = me.getGroupsForGrouper(records, grouper),
67502             length = groups.length,
67503             i;
67504
67505         if (grouperIndex + 1 < groupers.length) {
67506             for (i = 0; i < length; i++) {
67507                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
67508             }
67509         }
67510
67511         for (i = 0; i < length; i++) {
67512             groups[i].depth = grouperIndex;
67513         }
67514
67515         return groups;
67516     },
67517
67518     /**
67519      * @private
67520      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
67521      * this case grouping by genre and then author in a fictional books dataset):</p>
67522 <pre><code>
67523 [
67524     {
67525         name: 'Fantasy',
67526         depth: 0,
67527         records: [
67528             //book1, book2, book3, book4
67529         ],
67530         children: [
67531             {
67532                 name: 'Rowling',
67533                 depth: 1,
67534                 records: [
67535                     //book1, book2
67536                 ]
67537             },
67538             {
67539                 name: 'Tolkein',
67540                 depth: 1,
67541                 records: [
67542                     //book3, book4
67543                 ]
67544             }
67545         ]
67546     }
67547 ]
67548 </code></pre>
67549      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
67550      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
67551      * (defaults to true)
67552      * @return {Object[]} The group data
67553      */
67554     getGroupData: function(sort) {
67555         var me = this;
67556         if (sort !== false) {
67557             me.sort();
67558         }
67559
67560         return me.getGroupsForGrouperIndex(me.data.items, 0);
67561     },
67562
67563     /**
67564      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
67565      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
67566      * group by the first letter of a model's 'name' field, use the following code:</p>
67567 <pre><code>
67568 Ext.create('Ext.data.Store', {
67569     groupDir: 'ASC',
67570     getGroupString: function(instance) {
67571         return instance.get('name')[0];
67572     }
67573 });
67574 </code></pre>
67575      * @param {Ext.data.Model} instance The model instance
67576      * @return {String} The string to compare when forming groups
67577      */
67578     getGroupString: function(instance) {
67579         var group = this.groupers.first();
67580         if (group) {
67581             return instance.get(group.property);
67582         }
67583         return '';
67584     },
67585     /**
67586      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
67587      * See also <code>{@link #add}</code>.
67588      * @param {Number} index The start index at which to insert the passed Records.
67589      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
67590      */
67591     insert: function(index, records) {
67592         var me = this,
67593             sync = false,
67594             i,
67595             record,
67596             len;
67597
67598         records = [].concat(records);
67599         for (i = 0, len = records.length; i < len; i++) {
67600             record = me.createModel(records[i]);
67601             record.set(me.modelDefaults);
67602             // reassign the model in the array in case it wasn't created yet
67603             records[i] = record;
67604
67605             me.data.insert(index + i, record);
67606             record.join(me);
67607
67608             sync = sync || record.phantom === true;
67609         }
67610
67611         if (me.snapshot) {
67612             me.snapshot.addAll(records);
67613         }
67614
67615         me.fireEvent('add', me, records, index);
67616         me.fireEvent('datachanged', me);
67617         if (me.autoSync && sync) {
67618             me.sync();
67619         }
67620     },
67621
67622     /**
67623      * Adds Model instance to the Store. This method accepts either:
67624      *
67625      * - An array of Model instances or Model configuration objects.
67626      * - Any number of Model instance or Model configuration object arguments.
67627      *
67628      * The new Model instances will be added at the end of the existing collection.
67629      *
67630      * Sample usage:
67631      *
67632      *     myStore.add({some: 'data'}, {some: 'other data'});
67633      *
67634      * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
67635      * or Model configuration objects, or variable number of Model instance or config arguments.
67636      * @return {Ext.data.Model[]} The model instances that were added
67637      */
67638     add: function(records) {
67639         //accept both a single-argument array of records, or any number of record arguments
67640         if (!Ext.isArray(records)) {
67641             records = Array.prototype.slice.apply(arguments);
67642         }
67643
67644         var me = this,
67645             i = 0,
67646             length = records.length,
67647             record;
67648
67649         for (; i < length; i++) {
67650             record = me.createModel(records[i]);
67651             // reassign the model in the array in case it wasn't created yet
67652             records[i] = record;
67653         }
67654
67655         me.insert(me.data.length, records);
67656
67657         return records;
67658     },
67659
67660     /**
67661      * Converts a literal to a model, if it's not a model already
67662      * @private
67663      * @param record {Ext.data.Model/Object} The record to create
67664      * @return {Ext.data.Model}
67665      */
67666     createModel: function(record) {
67667         if (!record.isModel) {
67668             record = Ext.ModelManager.create(record, this.model);
67669         }
67670
67671         return record;
67672     },
67673
67674     /**
67675      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
67676      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
67677      * Returning <tt>false</tt> aborts and exits the iteration.
67678      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
67679      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
67680      */
67681     each: function(fn, scope) {
67682         this.data.each(fn, scope);
67683     },
67684
67685     /**
67686      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
67687      * 'datachanged' event after removal.
67688      * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
67689      */
67690     remove: function(records, /* private */ isMove) {
67691         if (!Ext.isArray(records)) {
67692             records = [records];
67693         }
67694
67695         /*
67696          * Pass the isMove parameter if we know we're going to be re-inserting this record
67697          */
67698         isMove = isMove === true;
67699         var me = this,
67700             sync = false,
67701             i = 0,
67702             length = records.length,
67703             isPhantom,
67704             index,
67705             record;
67706
67707         for (; i < length; i++) {
67708             record = records[i];
67709             index = me.data.indexOf(record);
67710
67711             if (me.snapshot) {
67712                 me.snapshot.remove(record);
67713             }
67714
67715             if (index > -1) {
67716                 isPhantom = record.phantom === true;
67717                 if (!isMove && !isPhantom) {
67718                     // don't push phantom records onto removed
67719                     me.removed.push(record);
67720                 }
67721
67722                 record.unjoin(me);
67723                 me.data.remove(record);
67724                 sync = sync || !isPhantom;
67725
67726                 me.fireEvent('remove', me, record, index);
67727             }
67728         }
67729
67730         me.fireEvent('datachanged', me);
67731         if (!isMove && me.autoSync && sync) {
67732             me.sync();
67733         }
67734     },
67735
67736     /**
67737      * Removes the model instance at the given index
67738      * @param {Number} index The record index
67739      */
67740     removeAt: function(index) {
67741         var record = this.getAt(index);
67742
67743         if (record) {
67744             this.remove(record);
67745         }
67746     },
67747
67748     /**
67749      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
67750      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
67751      * instances into the Store and calling an optional callback if required. Example usage:</p>
67752      *
67753 <pre><code>
67754 store.load({
67755     scope   : this,
67756     callback: function(records, operation, success) {
67757         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
67758         console.log(records);
67759     }
67760 });
67761 </code></pre>
67762      *
67763      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
67764      *
67765 <pre><code>
67766 store.load(function(records, operation, success) {
67767     console.log('loaded records');
67768 });
67769 </code></pre>
67770      *
67771      * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67772      */
67773     load: function(options) {
67774         var me = this;
67775
67776         options = options || {};
67777
67778         if (Ext.isFunction(options)) {
67779             options = {
67780                 callback: options
67781             };
67782         }
67783
67784         Ext.applyIf(options, {
67785             groupers: me.groupers.items,
67786             page: me.currentPage,
67787             start: (me.currentPage - 1) * me.pageSize,
67788             limit: me.pageSize,
67789             addRecords: false
67790         });
67791
67792         return me.callParent([options]);
67793     },
67794
67795     /**
67796      * @private
67797      * Called internally when a Proxy has completed a load request
67798      */
67799     onProxyLoad: function(operation) {
67800         var me = this,
67801             resultSet = operation.getResultSet(),
67802             records = operation.getRecords(),
67803             successful = operation.wasSuccessful();
67804
67805         if (resultSet) {
67806             me.totalCount = resultSet.total;
67807         }
67808
67809         if (successful) {
67810             me.loadRecords(records, operation);
67811         }
67812
67813         me.loading = false;
67814         me.fireEvent('load', me, records, successful);
67815
67816         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
67817         //People are definitely using this so can't deprecate safely until 2.x
67818         me.fireEvent('read', me, records, operation.wasSuccessful());
67819
67820         //this is a callback that would have been passed to the 'read' function and is optional
67821         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67822     },
67823
67824     /**
67825      * Create any new records when a write is returned from the server.
67826      * @private
67827      * @param {Ext.data.Model[]} records The array of new records
67828      * @param {Ext.data.Operation} operation The operation that just completed
67829      * @param {Boolean} success True if the operation was successful
67830      */
67831     onCreateRecords: function(records, operation, success) {
67832         if (success) {
67833             var i = 0,
67834                 data = this.data,
67835                 snapshot = this.snapshot,
67836                 length = records.length,
67837                 originalRecords = operation.records,
67838                 record,
67839                 original,
67840                 index;
67841
67842             /*
67843              * Loop over each record returned from the server. Assume they are
67844              * returned in order of how they were sent. If we find a matching
67845              * record, replace it with the newly created one.
67846              */
67847             for (; i < length; ++i) {
67848                 record = records[i];
67849                 original = originalRecords[i];
67850                 if (original) {
67851                     index = data.indexOf(original);
67852                     if (index > -1) {
67853                         data.removeAt(index);
67854                         data.insert(index, record);
67855                     }
67856                     if (snapshot) {
67857                         index = snapshot.indexOf(original);
67858                         if (index > -1) {
67859                             snapshot.removeAt(index);
67860                             snapshot.insert(index, record);
67861                         }
67862                     }
67863                     record.phantom = false;
67864                     record.join(this);
67865                 }
67866             }
67867         }
67868     },
67869
67870     /**
67871      * Update any records when a write is returned from the server.
67872      * @private
67873      * @param {Ext.data.Model[]} records The array of updated records
67874      * @param {Ext.data.Operation} operation The operation that just completed
67875      * @param {Boolean} success True if the operation was successful
67876      */
67877     onUpdateRecords: function(records, operation, success){
67878         if (success) {
67879             var i = 0,
67880                 length = records.length,
67881                 data = this.data,
67882                 snapshot = this.snapshot,
67883                 record;
67884
67885             for (; i < length; ++i) {
67886                 record = records[i];
67887                 data.replace(record);
67888                 if (snapshot) {
67889                     snapshot.replace(record);
67890                 }
67891                 record.join(this);
67892             }
67893         }
67894     },
67895
67896     /**
67897      * Remove any records when a write is returned from the server.
67898      * @private
67899      * @param {Ext.data.Model[]} records The array of removed records
67900      * @param {Ext.data.Operation} operation The operation that just completed
67901      * @param {Boolean} success True if the operation was successful
67902      */
67903     onDestroyRecords: function(records, operation, success){
67904         if (success) {
67905             var me = this,
67906                 i = 0,
67907                 length = records.length,
67908                 data = me.data,
67909                 snapshot = me.snapshot,
67910                 record;
67911
67912             for (; i < length; ++i) {
67913                 record = records[i];
67914                 record.unjoin(me);
67915                 data.remove(record);
67916                 if (snapshot) {
67917                     snapshot.remove(record);
67918                 }
67919             }
67920             me.removed = [];
67921         }
67922     },
67923
67924     //inherit docs
67925     getNewRecords: function() {
67926         return this.data.filterBy(this.filterNew).items;
67927     },
67928
67929     //inherit docs
67930     getUpdatedRecords: function() {
67931         return this.data.filterBy(this.filterUpdated).items;
67932     },
67933
67934     /**
67935      * Filters the loaded set of records by a given set of filters.
67936      *
67937      * Filtering by single field:
67938      *
67939      *     store.filter("email", /\.com$/);
67940      *
67941      * Using multiple filters:
67942      *
67943      *     store.filter([
67944      *         {property: "email", value: /\.com$/},
67945      *         {filterFn: function(item) { return item.get("age") > 10; }}
67946      *     ]);
67947      *
67948      * Using Ext.util.Filter instances instead of config objects
67949      * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
67950      *
67951      *     store.filter([
67952      *         Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
67953      *         Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
67954      *     ]);
67955      *
67956      * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
67957      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
67958      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
67959      * pass in a property string
67960      * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
67961      */
67962     filter: function(filters, value) {
67963         if (Ext.isString(filters)) {
67964             filters = {
67965                 property: filters,
67966                 value: value
67967             };
67968         }
67969
67970         var me = this,
67971             decoded = me.decodeFilters(filters),
67972             i = 0,
67973             doLocalSort = me.sortOnFilter && !me.remoteSort,
67974             length = decoded.length;
67975
67976         for (; i < length; i++) {
67977             me.filters.replace(decoded[i]);
67978         }
67979
67980         if (me.remoteFilter) {
67981             //the load function will pick up the new filters and request the filtered data from the proxy
67982             me.load();
67983         } else {
67984             /**
67985              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
67986              * records when a filter is removed or changed
67987              * @property snapshot
67988              * @type Ext.util.MixedCollection
67989              */
67990             if (me.filters.getCount()) {
67991                 me.snapshot = me.snapshot || me.data.clone();
67992                 me.data = me.data.filter(me.filters.items);
67993
67994                 if (doLocalSort) {
67995                     me.sort();
67996                 }
67997                 // fire datachanged event if it hasn't already been fired by doSort
67998                 if (!doLocalSort || me.sorters.length < 1) {
67999                     me.fireEvent('datachanged', me);
68000                 }
68001             }
68002         }
68003     },
68004
68005     /**
68006      * Revert to a view of the Record cache with no filtering applied.
68007      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
68008      * {@link #datachanged} event.
68009      */
68010     clearFilter: function(suppressEvent) {
68011         var me = this;
68012
68013         me.filters.clear();
68014
68015         if (me.remoteFilter) {
68016             me.load();
68017         } else if (me.isFiltered()) {
68018             me.data = me.snapshot.clone();
68019             delete me.snapshot;
68020
68021             if (suppressEvent !== true) {
68022                 me.fireEvent('datachanged', me);
68023             }
68024         }
68025     },
68026
68027     /**
68028      * Returns true if this store is currently filtered
68029      * @return {Boolean}
68030      */
68031     isFiltered: function() {
68032         var snapshot = this.snapshot;
68033         return !! snapshot && snapshot !== this.data;
68034     },
68035
68036     /**
68037      * Filter by a function. The specified function will be called for each
68038      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
68039      * otherwise it is filtered out.
68040      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68041      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68042      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68043      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68044      * </ul>
68045      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68046      */
68047     filterBy: function(fn, scope) {
68048         var me = this;
68049
68050         me.snapshot = me.snapshot || me.data.clone();
68051         me.data = me.queryBy(fn, scope || me);
68052         me.fireEvent('datachanged', me);
68053     },
68054
68055     /**
68056      * Query the cached records in this Store using a filtering function. The specified function
68057      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
68058      * included in the results.
68059      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68060      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68061      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68062      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68063      * </ul>
68064      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68065      * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
68066      **/
68067     queryBy: function(fn, scope) {
68068         var me = this,
68069         data = me.snapshot || me.data;
68070         return data.filterBy(fn, scope || me);
68071     },
68072
68073     /**
68074      * Loads an array of data straight into the Store.
68075      * 
68076      * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
68077      * processed by a reader). If your data requires processing to decode the data structure, use a
68078      * {@link Ext.data.proxy.Memory MemoryProxy} instead.
68079      * 
68080      * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
68081      * into model instances.
68082      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
68083      * to remove the old ones first.
68084      */
68085     loadData: function(data, append) {
68086         var model = this.model,
68087             length = data.length,
68088             newData = [],
68089             i,
68090             record;
68091
68092         //make sure each data element is an Ext.data.Model instance
68093         for (i = 0; i < length; i++) {
68094             record = data[i];
68095
68096             if (!(record instanceof Ext.data.Model)) {
68097                 record = Ext.ModelManager.create(record, model);
68098             }
68099             newData.push(record);
68100         }
68101
68102         this.loadRecords(newData, {addRecords: append});
68103     },
68104
68105
68106     /**
68107      * Loads data via the bound Proxy's reader
68108      *
68109      * Use this method if you are attempting to load data and want to utilize the configured data reader.
68110      *
68111      * @param {Object[]} data The full JSON object you'd like to load into the Data store.
68112      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
68113      * to remove the old ones first.
68114      */
68115     loadRawData : function(data, append) {
68116          var me      = this,
68117              result  = me.proxy.reader.read(data),
68118              records = result.records;
68119
68120          if (result.success) {
68121              me.loadRecords(records, { addRecords: append });
68122              me.fireEvent('load', me, records, true);
68123          }
68124      },
68125
68126
68127     /**
68128      * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
68129      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
68130      * @param {Ext.data.Model[]} records The array of records to load
68131      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
68132      */
68133     loadRecords: function(records, options) {
68134         var me     = this,
68135             i      = 0,
68136             length = records.length;
68137
68138         options = options || {};
68139
68140
68141         if (!options.addRecords) {
68142             delete me.snapshot;
68143             me.clearData();
68144         }
68145
68146         me.data.addAll(records);
68147
68148         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
68149         for (; i < length; i++) {
68150             if (options.start !== undefined) {
68151                 records[i].index = options.start + i;
68152
68153             }
68154             records[i].join(me);
68155         }
68156
68157         /*
68158          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
68159          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
68160          * datachanged event is fired by the call to this.add, above.
68161          */
68162         me.suspendEvents();
68163
68164         if (me.filterOnLoad && !me.remoteFilter) {
68165             me.filter();
68166         }
68167
68168         if (me.sortOnLoad && !me.remoteSort) {
68169             me.sort();
68170         }
68171
68172         me.resumeEvents();
68173         me.fireEvent('datachanged', me, records);
68174     },
68175
68176     // PAGING METHODS
68177     /**
68178      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
68179      * load operation, passing in calculated 'start' and 'limit' params
68180      * @param {Number} page The number of the page to load
68181      * @param {Object} options See options for {@link #load}
68182      */
68183     loadPage: function(page, options) {
68184         var me = this;
68185         options = Ext.apply({}, options);
68186
68187         me.currentPage = page;
68188
68189         me.read(Ext.applyIf(options, {
68190             page: page,
68191             start: (page - 1) * me.pageSize,
68192             limit: me.pageSize,
68193             addRecords: !me.clearOnPageLoad
68194         }));
68195     },
68196
68197     /**
68198      * Loads the next 'page' in the current data set
68199      * @param {Object} options See options for {@link #load}
68200      */
68201     nextPage: function(options) {
68202         this.loadPage(this.currentPage + 1, options);
68203     },
68204
68205     /**
68206      * Loads the previous 'page' in the current data set
68207      * @param {Object} options See options for {@link #load}
68208      */
68209     previousPage: function(options) {
68210         this.loadPage(this.currentPage - 1, options);
68211     },
68212
68213     // private
68214     clearData: function() {
68215         var me = this;
68216         me.data.each(function(record) {
68217             record.unjoin(me);
68218         });
68219
68220         me.data.clear();
68221     },
68222
68223     // Buffering
68224     /**
68225      * Prefetches data into the store using its configured {@link #proxy}.
68226      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
68227      * See {@link #load}
68228      */
68229     prefetch: function(options) {
68230         var me = this,
68231             operation,
68232             requestId = me.getRequestId();
68233
68234         options = options || {};
68235
68236         Ext.applyIf(options, {
68237             action : 'read',
68238             filters: me.filters.items,
68239             sorters: me.sorters.items,
68240             requestId: requestId
68241         });
68242         me.pendingRequests.push(requestId);
68243
68244         operation = Ext.create('Ext.data.Operation', options);
68245
68246         // HACK to implement loadMask support.
68247         //if (operation.blocking) {
68248         //    me.fireEvent('beforeload', me, operation);
68249         //}
68250         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
68251             me.loading = true;
68252             me.proxy.read(operation, me.onProxyPrefetch, me);
68253         }
68254
68255         return me;
68256     },
68257
68258     /**
68259      * Prefetches a page of data.
68260      * @param {Number} page The page to prefetch
68261      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
68262      * See {@link #load}
68263      */
68264     prefetchPage: function(page, options) {
68265         var me = this,
68266             pageSize = me.pageSize,
68267             start = (page - 1) * me.pageSize,
68268             end = start + pageSize;
68269
68270         // Currently not requesting this page and range isn't already satisified
68271         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
68272             options = options || {};
68273             me.pagesRequested.push(page);
68274             Ext.applyIf(options, {
68275                 page : page,
68276                 start: start,
68277                 limit: pageSize,
68278                 callback: me.onWaitForGuarantee,
68279                 scope: me
68280             });
68281
68282             me.prefetch(options);
68283         }
68284
68285     },
68286
68287     /**
68288      * Returns a unique requestId to track requests.
68289      * @private
68290      */
68291     getRequestId: function() {
68292         this.requestSeed = this.requestSeed || 1;
68293         return this.requestSeed++;
68294     },
68295
68296     /**
68297      * Called after the configured proxy completes a prefetch operation.
68298      * @private
68299      * @param {Ext.data.Operation} operation The operation that completed
68300      */
68301     onProxyPrefetch: function(operation) {
68302         var me         = this,
68303             resultSet  = operation.getResultSet(),
68304             records    = operation.getRecords(),
68305
68306             successful = operation.wasSuccessful();
68307
68308         if (resultSet) {
68309             me.totalCount = resultSet.total;
68310             me.fireEvent('totalcountchange', me.totalCount);
68311         }
68312
68313         if (successful) {
68314             me.cacheRecords(records, operation);
68315         }
68316         Ext.Array.remove(me.pendingRequests, operation.requestId);
68317         if (operation.page) {
68318             Ext.Array.remove(me.pagesRequested, operation.page);
68319         }
68320
68321         me.loading = false;
68322         me.fireEvent('prefetch', me, records, successful, operation);
68323
68324         // HACK to support loadMask
68325         if (operation.blocking) {
68326             me.fireEvent('load', me, records, successful);
68327         }
68328
68329         //this is a callback that would have been passed to the 'read' function and is optional
68330         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
68331     },
68332
68333     /**
68334      * Caches the records in the prefetch and stripes them with their server-side
68335      * index.
68336      * @private
68337      * @param {Ext.data.Model[]} records The records to cache
68338      * @param {Ext.data.Operation} The associated operation
68339      */
68340     cacheRecords: function(records, operation) {
68341         var me     = this,
68342             i      = 0,
68343             length = records.length,
68344             start  = operation ? operation.start : 0;
68345
68346         if (!Ext.isDefined(me.totalCount)) {
68347             me.totalCount = records.length;
68348             me.fireEvent('totalcountchange', me.totalCount);
68349         }
68350
68351         for (; i < length; i++) {
68352             // this is the true index, not the viewIndex
68353             records[i].index = start + i;
68354         }
68355
68356         me.prefetchData.addAll(records);
68357         if (me.purgePageCount) {
68358             me.purgeRecords();
68359         }
68360
68361     },
68362
68363
68364     /**
68365      * Purge the least recently used records in the prefetch if the purgeCount
68366      * has been exceeded.
68367      */
68368     purgeRecords: function() {
68369         var me = this,
68370             prefetchCount = me.prefetchData.getCount(),
68371             purgeCount = me.purgePageCount * me.pageSize,
68372             numRecordsToPurge = prefetchCount - purgeCount - 1,
68373             i = 0;
68374
68375         for (; i <= numRecordsToPurge; i++) {
68376             me.prefetchData.removeAt(0);
68377         }
68378     },
68379
68380     /**
68381      * Determines if the range has already been satisfied in the prefetchData.
68382      * @private
68383      * @param {Number} start The start index
68384      * @param {Number} end The end index in the range
68385      */
68386     rangeSatisfied: function(start, end) {
68387         var me = this,
68388             i = start,
68389             satisfied = true;
68390
68391         for (; i < end; i++) {
68392             if (!me.prefetchData.getByKey(i)) {
68393                 satisfied = false;
68394                 if (end - i > me.pageSize) {
68395                     Ext.Error.raise("A single page prefetch could never satisfy this request.");
68396                 }
68397                 break;
68398             }
68399         }
68400         return satisfied;
68401     },
68402
68403     /**
68404      * Determines the page from a record index
68405      * @param {Number} index The record index
68406      * @return {Number} The page the record belongs to
68407      */
68408     getPageFromRecordIndex: function(index) {
68409         return Math.floor(index / this.pageSize) + 1;
68410     },
68411
68412     /**
68413      * Handles a guaranteed range being loaded
68414      * @private
68415      */
68416     onGuaranteedRange: function() {
68417         var me = this,
68418             totalCount = me.getTotalCount(),
68419             start = me.requestStart,
68420             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
68421             range = [],
68422             record,
68423             i = start;
68424
68425         end = Math.max(0, end);
68426
68427         if (start > end) {
68428             Ext.log({
68429                 level: 'warn',
68430                 msg: 'Start (' + start + ') was greater than end (' + end +
68431                     ') for the range of records requested (' + me.requestStart + '-' +
68432                     me.requestEnd + ')' + (this.storeId ? ' from store "' + this.storeId + '"' : '')
68433             });
68434         }
68435
68436         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
68437             me.guaranteedStart = start;
68438             me.guaranteedEnd = end;
68439
68440             for (; i <= end; i++) {
68441                 record = me.prefetchData.getByKey(i);
68442 //                if (!record) {
68443 //                    Ext.log('Record with key "' + i + '" was not found and store said it was guaranteed');
68444 //                }
68445                 if (record) {
68446                     range.push(record);
68447                 }
68448             }
68449             me.fireEvent('guaranteedrange', range, start, end);
68450             if (me.cb) {
68451                 me.cb.call(me.scope || me, range);
68452             }
68453         }
68454
68455         me.unmask();
68456     },
68457
68458     // hack to support loadmask
68459     mask: function() {
68460         this.masked = true;
68461         this.fireEvent('beforeload');
68462     },
68463
68464     // hack to support loadmask
68465     unmask: function() {
68466         if (this.masked) {
68467             this.fireEvent('load');
68468         }
68469     },
68470
68471     /**
68472      * Returns the number of pending requests out.
68473      */
68474     hasPendingRequests: function() {
68475         return this.pendingRequests.length;
68476     },
68477
68478
68479     // wait until all requests finish, until guaranteeing the range.
68480     onWaitForGuarantee: function() {
68481         if (!this.hasPendingRequests()) {
68482             this.onGuaranteedRange();
68483         }
68484     },
68485
68486     /**
68487      * Guarantee a specific range, this will load the store with a range (that
68488      * must be the pageSize or smaller) and take care of any loading that may
68489      * be necessary.
68490      */
68491     guaranteeRange: function(start, end, cb, scope) {
68492         if (start && end) {
68493             if (end - start > this.pageSize) {
68494                 Ext.Error.raise({
68495                     start: start,
68496                     end: end,
68497                     pageSize: this.pageSize,
68498                     msg: "Requested a bigger range than the specified pageSize"
68499                 });
68500             }
68501         }
68502
68503         end = (end > this.totalCount) ? this.totalCount - 1 : end;
68504
68505         var me = this,
68506             i = start,
68507             prefetchData = me.prefetchData,
68508             range = [],
68509             startLoaded = !!prefetchData.getByKey(start),
68510             endLoaded = !!prefetchData.getByKey(end),
68511             startPage = me.getPageFromRecordIndex(start),
68512             endPage = me.getPageFromRecordIndex(end);
68513
68514         me.cb = cb;
68515         me.scope = scope;
68516
68517         me.requestStart = start;
68518         me.requestEnd = end;
68519         // neither beginning or end are loaded
68520         if (!startLoaded || !endLoaded) {
68521             // same page, lets load it
68522             if (startPage === endPage) {
68523                 me.mask();
68524                 me.prefetchPage(startPage, {
68525                     //blocking: true,
68526                     callback: me.onWaitForGuarantee,
68527                     scope: me
68528                 });
68529             // need to load two pages
68530             } else {
68531                 me.mask();
68532                 me.prefetchPage(startPage, {
68533                     //blocking: true,
68534                     callback: me.onWaitForGuarantee,
68535                     scope: me
68536                 });
68537                 me.prefetchPage(endPage, {
68538                     //blocking: true,
68539                     callback: me.onWaitForGuarantee,
68540                     scope: me
68541                 });
68542             }
68543         // Request was already satisfied via the prefetch
68544         } else {
68545             me.onGuaranteedRange();
68546         }
68547     },
68548
68549     // because prefetchData is stored by index
68550     // this invalidates all of the prefetchedData
68551     sort: function() {
68552         var me = this,
68553             prefetchData = me.prefetchData,
68554             sorters,
68555             start,
68556             end,
68557             range;
68558
68559         if (me.buffered) {
68560             if (me.remoteSort) {
68561                 prefetchData.clear();
68562                 me.callParent(arguments);
68563             } else {
68564                 sorters = me.getSorters();
68565                 start = me.guaranteedStart;
68566                 end = me.guaranteedEnd;
68567
68568                 if (sorters.length) {
68569                     prefetchData.sort(sorters);
68570                     range = prefetchData.getRange();
68571                     prefetchData.clear();
68572                     me.cacheRecords(range);
68573                     delete me.guaranteedStart;
68574                     delete me.guaranteedEnd;
68575                     me.guaranteeRange(start, end);
68576                 }
68577                 me.callParent(arguments);
68578             }
68579         } else {
68580             me.callParent(arguments);
68581         }
68582     },
68583
68584     // overriden to provide striping of the indexes as sorting occurs.
68585     // this cannot be done inside of sort because datachanged has already
68586     // fired and will trigger a repaint of the bound view.
68587     doSort: function(sorterFn) {
68588         var me = this;
68589         if (me.remoteSort) {
68590             //the load function will pick up the new sorters and request the sorted data from the proxy
68591             me.load();
68592         } else {
68593             me.data.sortBy(sorterFn);
68594             if (!me.buffered) {
68595                 var range = me.getRange(),
68596                     ln = range.length,
68597                     i  = 0;
68598                 for (; i < ln; i++) {
68599                     range[i].index = i;
68600                 }
68601             }
68602             me.fireEvent('datachanged', me);
68603         }
68604     },
68605
68606     /**
68607      * Finds the index of the first matching Record in this store by a specific field value.
68608      * @param {String} fieldName The name of the Record field to test.
68609      * @param {String/RegExp} value Either a string that the field value
68610      * should begin with, or a RegExp to test against the field.
68611      * @param {Number} startIndex (optional) The index to start searching at
68612      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
68613      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
68614      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
68615      * @return {Number} The matched index or -1
68616      */
68617     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
68618         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
68619         return fn ? this.data.findIndexBy(fn, null, start) : -1;
68620     },
68621
68622     /**
68623      * Finds the first matching Record in this store by a specific field value.
68624      * @param {String} fieldName The name of the Record field to test.
68625      * @param {String/RegExp} value Either a string that the field value
68626      * should begin with, or a RegExp to test against the field.
68627      * @param {Number} startIndex (optional) The index to start searching at
68628      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
68629      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
68630      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
68631      * @return {Ext.data.Model} The matched record or null
68632      */
68633     findRecord: function() {
68634         var me = this,
68635             index = me.find.apply(me, arguments);
68636         return index !== -1 ? me.getAt(index) : null;
68637     },
68638
68639     /**
68640      * @private
68641      * Returns a filter function used to test a the given property's value. Defers most of the work to
68642      * Ext.util.MixedCollection's createValueMatcher function
68643      * @param {String} property The property to create the filter function for
68644      * @param {String/RegExp} value The string/regex to compare the property value to
68645      * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
68646      * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
68647      * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
68648      * Ignored if anyMatch is true.
68649      */
68650     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
68651         if (Ext.isEmpty(value)) {
68652             return false;
68653         }
68654         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
68655         return function(r) {
68656             return value.test(r.data[property]);
68657         };
68658     },
68659
68660     /**
68661      * Finds the index of the first matching Record in this store by a specific field value.
68662      * @param {String} fieldName The name of the Record field to test.
68663      * @param {Object} value The value to match the field against.
68664      * @param {Number} startIndex (optional) The index to start searching at
68665      * @return {Number} The matched index or -1
68666      */
68667     findExact: function(property, value, start) {
68668         return this.data.findIndexBy(function(rec) {
68669             return rec.get(property) == value;
68670         },
68671         this, start);
68672     },
68673
68674     /**
68675      * Find the index of the first matching Record in this Store by a function.
68676      * If the function returns <tt>true</tt> it is considered a match.
68677      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68678      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68679      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68680      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68681      * </ul>
68682      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68683      * @param {Number} startIndex (optional) The index to start searching at
68684      * @return {Number} The matched index or -1
68685      */
68686     findBy: function(fn, scope, start) {
68687         return this.data.findIndexBy(fn, scope, start);
68688     },
68689
68690     /**
68691      * Collects unique values for a particular dataIndex from this store.
68692      * @param {String} dataIndex The property to collect
68693      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
68694      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
68695      * @return {Object[]} An array of the unique values
68696      **/
68697     collect: function(dataIndex, allowNull, bypassFilter) {
68698         var me = this,
68699             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
68700
68701         return data.collect(dataIndex, 'data', allowNull);
68702     },
68703
68704     /**
68705      * Gets the number of cached records.
68706      * <p>If using paging, this may not be the total size of the dataset. If the data object
68707      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
68708      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
68709      * @return {Number} The number of Records in the Store's cache.
68710      */
68711     getCount: function() {
68712         return this.data.length || 0;
68713     },
68714
68715     /**
68716      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
68717      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
68718      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
68719      * could be loaded into the Store if the Store contained all data
68720      * @return {Number} The total number of Model instances available via the Proxy
68721      */
68722     getTotalCount: function() {
68723         return this.totalCount;
68724     },
68725
68726     /**
68727      * Get the Record at the specified index.
68728      * @param {Number} index The index of the Record to find.
68729      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
68730      */
68731     getAt: function(index) {
68732         return this.data.getAt(index);
68733     },
68734
68735     /**
68736      * Returns a range of Records between specified indices.
68737      * @param {Number} [startIndex=0] The starting index
68738      * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
68739      * @return {Ext.data.Model[]} An array of Records
68740      */
68741     getRange: function(start, end) {
68742         return this.data.getRange(start, end);
68743     },
68744
68745     /**
68746      * Get the Record with the specified id.
68747      * @param {String} id The id of the Record to find.
68748      * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
68749      */
68750     getById: function(id) {
68751         return (this.snapshot || this.data).findBy(function(record) {
68752             return record.getId() === id;
68753         });
68754     },
68755
68756     /**
68757      * Get the index within the cache of the passed Record.
68758      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68759      * @return {Number} The index of the passed Record. Returns -1 if not found.
68760      */
68761     indexOf: function(record) {
68762         return this.data.indexOf(record);
68763     },
68764
68765
68766     /**
68767      * Get the index within the entire dataset. From 0 to the totalCount.
68768      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68769      * @return {Number} The index of the passed Record. Returns -1 if not found.
68770      */
68771     indexOfTotal: function(record) {
68772         var index = record.index;
68773         if (index || index === 0) {
68774             return index;
68775         }
68776         return this.indexOf(record);
68777     },
68778
68779     /**
68780      * Get the index within the cache of the Record with the passed id.
68781      * @param {String} id The id of the Record to find.
68782      * @return {Number} The index of the Record. Returns -1 if not found.
68783      */
68784     indexOfId: function(id) {
68785         return this.indexOf(this.getById(id));
68786     },
68787
68788     /**
68789      * Remove all items from the store.
68790      * @param {Boolean} silent Prevent the `clear` event from being fired.
68791      */
68792     removeAll: function(silent) {
68793         var me = this;
68794
68795         me.clearData();
68796         if (me.snapshot) {
68797             me.snapshot.clear();
68798         }
68799         if (silent !== true) {
68800             me.fireEvent('clear', me);
68801         }
68802     },
68803
68804     /*
68805      * Aggregation methods
68806      */
68807
68808     /**
68809      * Convenience function for getting the first model instance in the store
68810      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68811      * in the store. The value returned will be an object literal with the key being the group
68812      * name and the first record being the value. The grouped parameter is only honored if
68813      * the store has a groupField.
68814      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
68815      */
68816     first: function(grouped) {
68817         var me = this;
68818
68819         if (grouped && me.isGrouped()) {
68820             return me.aggregate(function(records) {
68821                 return records.length ? records[0] : undefined;
68822             }, me, true);
68823         } else {
68824             return me.data.first();
68825         }
68826     },
68827
68828     /**
68829      * Convenience function for getting the last model instance in the store
68830      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68831      * in the store. The value returned will be an object literal with the key being the group
68832      * name and the last record being the value. The grouped parameter is only honored if
68833      * the store has a groupField.
68834      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
68835      */
68836     last: function(grouped) {
68837         var me = this;
68838
68839         if (grouped && me.isGrouped()) {
68840             return me.aggregate(function(records) {
68841                 var len = records.length;
68842                 return len ? records[len - 1] : undefined;
68843             }, me, true);
68844         } else {
68845             return me.data.last();
68846         }
68847     },
68848
68849     /**
68850      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
68851      * and <tt>end</tt> and returns the result.
68852      * @param {String} field A field in each record
68853      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68854      * in the store. The value returned will be an object literal with the key being the group
68855      * name and the sum for that group being the value. The grouped parameter is only honored if
68856      * the store has a groupField.
68857      * @return {Number} The sum
68858      */
68859     sum: function(field, grouped) {
68860         var me = this;
68861
68862         if (grouped && me.isGrouped()) {
68863             return me.aggregate(me.getSum, me, true, [field]);
68864         } else {
68865             return me.getSum(me.data.items, field);
68866         }
68867     },
68868
68869     // @private, see sum
68870     getSum: function(records, field) {
68871         var total = 0,
68872             i = 0,
68873             len = records.length;
68874
68875         for (; i < len; ++i) {
68876             total += records[i].get(field);
68877         }
68878
68879         return total;
68880     },
68881
68882     /**
68883      * Gets the count of items in the store.
68884      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68885      * in the store. The value returned will be an object literal with the key being the group
68886      * name and the count for each group being the value. The grouped parameter is only honored if
68887      * the store has a groupField.
68888      * @return {Number} the count
68889      */
68890     count: function(grouped) {
68891         var me = this;
68892
68893         if (grouped && me.isGrouped()) {
68894             return me.aggregate(function(records) {
68895                 return records.length;
68896             }, me, true);
68897         } else {
68898             return me.getCount();
68899         }
68900     },
68901
68902     /**
68903      * Gets the minimum value in the store.
68904      * @param {String} field The field in each record
68905      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68906      * in the store. The value returned will be an object literal with the key being the group
68907      * name and the minimum in the group being the value. The grouped parameter is only honored if
68908      * the store has a groupField.
68909      * @return {Object} The minimum value, if no items exist, undefined.
68910      */
68911     min: function(field, grouped) {
68912         var me = this;
68913
68914         if (grouped && me.isGrouped()) {
68915             return me.aggregate(me.getMin, me, true, [field]);
68916         } else {
68917             return me.getMin(me.data.items, field);
68918         }
68919     },
68920
68921     // @private, see min
68922     getMin: function(records, field){
68923         var i = 1,
68924             len = records.length,
68925             value, min;
68926
68927         if (len > 0) {
68928             min = records[0].get(field);
68929         }
68930
68931         for (; i < len; ++i) {
68932             value = records[i].get(field);
68933             if (value < min) {
68934                 min = value;
68935             }
68936         }
68937         return min;
68938     },
68939
68940     /**
68941      * Gets the maximum value in the store.
68942      * @param {String} field The field in each record
68943      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68944      * in the store. The value returned will be an object literal with the key being the group
68945      * name and the maximum in the group being the value. The grouped parameter is only honored if
68946      * the store has a groupField.
68947      * @return {Object} The maximum value, if no items exist, undefined.
68948      */
68949     max: function(field, grouped) {
68950         var me = this;
68951
68952         if (grouped && me.isGrouped()) {
68953             return me.aggregate(me.getMax, me, true, [field]);
68954         } else {
68955             return me.getMax(me.data.items, field);
68956         }
68957     },
68958
68959     // @private, see max
68960     getMax: function(records, field) {
68961         var i = 1,
68962             len = records.length,
68963             value,
68964             max;
68965
68966         if (len > 0) {
68967             max = records[0].get(field);
68968         }
68969
68970         for (; i < len; ++i) {
68971             value = records[i].get(field);
68972             if (value > max) {
68973                 max = value;
68974             }
68975         }
68976         return max;
68977     },
68978
68979     /**
68980      * Gets the average value in the store.
68981      * @param {String} field The field in each record
68982      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68983      * in the store. The value returned will be an object literal with the key being the group
68984      * name and the group average being the value. The grouped parameter is only honored if
68985      * the store has a groupField.
68986      * @return {Object} The average value, if no items exist, 0.
68987      */
68988     average: function(field, grouped) {
68989         var me = this;
68990         if (grouped && me.isGrouped()) {
68991             return me.aggregate(me.getAverage, me, true, [field]);
68992         } else {
68993             return me.getAverage(me.data.items, field);
68994         }
68995     },
68996
68997     // @private, see average
68998     getAverage: function(records, field) {
68999         var i = 0,
69000             len = records.length,
69001             sum = 0;
69002
69003         if (records.length > 0) {
69004             for (; i < len; ++i) {
69005                 sum += records[i].get(field);
69006             }
69007             return sum / len;
69008         }
69009         return 0;
69010     },
69011
69012     /**
69013      * Runs the aggregate function for all the records in the store.
69014      * @param {Function} fn The function to execute. The function is called with a single parameter,
69015      * an array of records for that group.
69016      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
69017      * @param {Boolean} grouped (Optional) True to perform the operation for each group
69018      * in the store. The value returned will be an object literal with the key being the group
69019      * name and the group average being the value. The grouped parameter is only honored if
69020      * the store has a groupField.
69021      * @param {Array} args (optional) Any arguments to append to the function call
69022      * @return {Object} An object literal with the group names and their appropriate values.
69023      */
69024     aggregate: function(fn, scope, grouped, args) {
69025         args = args || [];
69026         if (grouped && this.isGrouped()) {
69027             var groups = this.getGroups(),
69028                 i = 0,
69029                 len = groups.length,
69030                 out = {},
69031                 group;
69032
69033             for (; i < len; ++i) {
69034                 group = groups[i];
69035                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
69036             }
69037             return out;
69038         } else {
69039             return fn.apply(scope || this, [this.data.items].concat(args));
69040         }
69041     }
69042 }, function() {
69043     // A dummy empty store with a fieldless Model defined in it.
69044     // Just for binding to Views which are instantiated with no Store defined.
69045     // They will be able to run and render fine, and be bound to a generated Store later.
69046     Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
69047 });
69048
69049 /**
69050  * @author Ed Spencer
69051  * @class Ext.data.JsonStore
69052  * @extends Ext.data.Store
69053  * @ignore
69054  *
69055  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
69056  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
69057  *
69058  * <p>A store configuration would be something like:</p>
69059  *
69060 <pre><code>
69061 var store = new Ext.data.JsonStore({
69062     // store configs
69063     autoDestroy: true,
69064     storeId: 'myStore',
69065
69066     proxy: {
69067         type: 'ajax',
69068         url: 'get-images.php',
69069         reader: {
69070             type: 'json',
69071             root: 'images',
69072             idProperty: 'name'
69073         }
69074     },
69075
69076     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
69077     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
69078 });
69079 </code></pre>
69080  *
69081  * <p>This store is configured to consume a returned object of the form:<pre><code>
69082 {
69083     images: [
69084         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
69085         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
69086     ]
69087 }
69088 </code></pre>
69089  *
69090  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
69091  *
69092  * @xtype jsonstore
69093  */
69094 Ext.define('Ext.data.JsonStore',  {
69095     extend: 'Ext.data.Store',
69096     alias: 'store.json',
69097
69098     /**
69099      * @cfg {Ext.data.DataReader} reader @hide
69100      */
69101     constructor: function(config) {
69102         config = config || {};
69103
69104         Ext.applyIf(config, {
69105             proxy: {
69106                 type  : 'ajax',
69107                 reader: 'json',
69108                 writer: 'json'
69109             }
69110         });
69111
69112         this.callParent([config]);
69113     }
69114 });
69115
69116 /**
69117  * @class Ext.chart.axis.Time
69118  * @extends Ext.chart.axis.Numeric
69119  *
69120  * A type of axis whose units are measured in time values. Use this axis
69121  * for listing dates that you will want to group or dynamically change.
69122  * If you just want to display dates as categories then use the
69123  * Category class for axis instead.
69124  *
69125  * For example:
69126  *
69127  *     axes: [{
69128  *         type: 'Time',
69129  *         position: 'bottom',
69130  *         fields: 'date',
69131  *         title: 'Day',
69132  *         dateFormat: 'M d',
69133  *
69134  *         constrain: true,
69135  *         fromDate: new Date('1/1/11'),
69136  *         toDate: new Date('1/7/11')
69137  *     }]
69138  *
69139  * In this example we're creating a time axis that has as title *Day*.
69140  * The field the axis is bound to is `date`.
69141  * The date format to use to display the text for the axis labels is `M d`
69142  * which is a three letter month abbreviation followed by the day number.
69143  * The time axis will show values for dates between `fromDate` and `toDate`.
69144  * Since `constrain` is set to true all other values for other dates not between
69145  * the fromDate and toDate will not be displayed.
69146  *
69147  */
69148 Ext.define('Ext.chart.axis.Time', {
69149
69150     /* Begin Definitions */
69151
69152     extend: 'Ext.chart.axis.Numeric',
69153
69154     alternateClassName: 'Ext.chart.TimeAxis',
69155
69156     alias: 'axis.time',
69157
69158     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
69159
69160     /* End Definitions */
69161
69162     /**
69163      * @cfg {String/Boolean} dateFormat
69164      * Indicates the format the date will be rendered on.
69165      * For example: 'M d' will render the dates as 'Jan 30', etc.
69166      * For a list of possible format strings see {@link Ext.Date Date}
69167      */
69168     dateFormat: false,
69169
69170     /**
69171      * @cfg {Date} fromDate The starting date for the time axis.
69172      */
69173     fromDate: false,
69174
69175     /**
69176      * @cfg {Date} toDate The ending date for the time axis.
69177      */
69178     toDate: false,
69179
69180     /**
69181      * @cfg {Array/Boolean} step
69182      * An array with two components: The first is the unit of the step (day, month, year, etc).
69183      * The second one is the number of units for the step (1, 2, etc.).
69184      * Defaults to `[Ext.Date.DAY, 1]`.
69185      */
69186     step: [Ext.Date.DAY, 1],
69187     
69188     /**
69189      * @cfg {Boolean} constrain
69190      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
69191      * If false, the time axis will adapt to the new values by adding/removing steps.
69192      */
69193     constrain: false,
69194
69195     // Avoid roundtoDecimal call in Numeric Axis's constructor
69196     roundToDecimal: false,
69197     
69198     constructor: function (config) {
69199         var me = this, label, f, df;
69200         me.callParent([config]);
69201         label = me.label || {};
69202         df = this.dateFormat;
69203         if (df) {
69204             if (label.renderer) {
69205                 f = label.renderer;
69206                 label.renderer = function(v) {
69207                     v = f(v);
69208                     return Ext.Date.format(new Date(f(v)), df);
69209                 };
69210             } else {
69211                 label.renderer = function(v) {
69212                     return Ext.Date.format(new Date(v >> 0), df);
69213                 };
69214             }
69215         }
69216     },
69217
69218     doConstrain: function () {
69219         var me = this,
69220             store = me.chart.store,
69221             data = [],
69222             series = me.chart.series.items,
69223             math = Math,
69224             mmax = math.max,
69225             mmin = math.min,
69226             fields = me.fields,
69227             ln = fields.length,
69228             range = me.getRange(),
69229             min = range.min, max = range.max, i, l, excludes = [],
69230             value, values, rec, data = [];
69231         for (i = 0, l = series.length; i < l; i++) {
69232             excludes[i] = series[i].__excludes;
69233         }
69234         store.each(function(record) {
69235             for (i = 0; i < ln; i++) {
69236                 if (excludes[i]) {
69237                     continue;
69238                 }
69239                 value = record.get(fields[i]);
69240                 if (+value < +min) return;
69241                 if (+value > +max) return;
69242             }
69243             data.push(record);
69244         })
69245         me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
69246     },
69247
69248     // Before rendering, set current default step count to be number of records.
69249     processView: function () {
69250         var me = this;
69251         if (me.fromDate) {
69252             me.minimum = +me.fromDate;
69253         }
69254         if (me.toDate) {
69255             me.maximum = +me.toDate;
69256         }
69257         if (me.constrain) {
69258             me.doConstrain();
69259         }
69260      },
69261
69262     // @private modifies the store and creates the labels for the axes.
69263     calcEnds: function() {
69264         var me = this, range, step = me.step;
69265         if (step) {
69266             range = me.getRange();
69267             range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
69268             if (me.minimum) {
69269                 range.from = me.minimum;
69270             }
69271             if (me.maximum) {
69272                 range.to = me.maximum;
69273             }
69274             range.step = (range.to - range.from) / range.steps;
69275             return range;
69276         } else {
69277             return me.callParent(arguments);
69278         }
69279     }
69280  });
69281
69282
69283 /**
69284  * @class Ext.chart.series.Series
69285  *
69286  * Series is the abstract class containing the common logic to all chart series. Series includes
69287  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
69288  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
69289  *
69290  * ## Listeners
69291  *
69292  * The series class supports listeners via the Observable syntax. Some of these listeners are:
69293  *
69294  *  - `itemmouseup` When the user interacts with a marker.
69295  *  - `itemmousedown` When the user interacts with a marker.
69296  *  - `itemmousemove` When the user iteracts with a marker.
69297  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
69298  *
69299  * For example:
69300  *
69301  *     series: [{
69302  *             type: 'column',
69303  *             axis: 'left',
69304  *             listeners: {
69305  *                     'afterrender': function() {
69306  *                             console('afterrender');
69307  *                     }
69308  *             },
69309  *             xField: 'category',
69310  *             yField: 'data1'
69311  *     }]
69312  */
69313 Ext.define('Ext.chart.series.Series', {
69314
69315     /* Begin Definitions */
69316
69317     mixins: {
69318         observable: 'Ext.util.Observable',
69319         labels: 'Ext.chart.Label',
69320         highlights: 'Ext.chart.Highlight',
69321         tips: 'Ext.chart.Tip',
69322         callouts: 'Ext.chart.Callout'
69323     },
69324
69325     /* End Definitions */
69326
69327     /**
69328      * @cfg {Boolean/Object} highlight
69329      * If set to `true` it will highlight the markers or the series when hovering
69330      * with the mouse. This parameter can also be an object with the same style
69331      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
69332      * styles to markers and series.
69333      */
69334
69335     /**
69336      * @cfg {Object} tips
69337      * Add tooltips to the visualization's markers. The options for the tips are the
69338      * same configuration used with {@link Ext.tip.ToolTip}. For example:
69339      *
69340      *     tips: {
69341      *       trackMouse: true,
69342      *       width: 140,
69343      *       height: 28,
69344      *       renderer: function(storeItem, item) {
69345      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
69346      *       }
69347      *     },
69348      */
69349
69350     /**
69351      * @cfg {String} type
69352      * The type of series. Set in subclasses.
69353      */
69354     type: null,
69355
69356     /**
69357      * @cfg {String} title
69358      * The human-readable name of the series.
69359      */
69360     title: null,
69361
69362     /**
69363      * @cfg {Boolean} showInLegend
69364      * Whether to show this series in the legend.
69365      */
69366     showInLegend: true,
69367
69368     /**
69369      * @cfg {Function} renderer
69370      * A function that can be overridden to set custom styling properties to each rendered element.
69371      * Passes in (sprite, record, attributes, index, store) to the function.
69372      */
69373     renderer: function(sprite, record, attributes, index, store) {
69374         return attributes;
69375     },
69376
69377     /**
69378      * @cfg {Array} shadowAttributes
69379      * An array with shadow attributes
69380      */
69381     shadowAttributes: null,
69382
69383     //@private triggerdrawlistener flag
69384     triggerAfterDraw: false,
69385
69386     /**
69387      * @cfg {Object} listeners
69388      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
69389      *
69390      *  - itemmouseover
69391      *  - itemmouseout
69392      *  - itemmousedown
69393      *  - itemmouseup
69394      */
69395
69396     constructor: function(config) {
69397         var me = this;
69398         if (config) {
69399             Ext.apply(me, config);
69400         }
69401
69402         me.shadowGroups = [];
69403
69404         me.mixins.labels.constructor.call(me, config);
69405         me.mixins.highlights.constructor.call(me, config);
69406         me.mixins.tips.constructor.call(me, config);
69407         me.mixins.callouts.constructor.call(me, config);
69408
69409         me.addEvents({
69410             scope: me,
69411             itemmouseover: true,
69412             itemmouseout: true,
69413             itemmousedown: true,
69414             itemmouseup: true,
69415             mouseleave: true,
69416             afterdraw: true,
69417
69418             /**
69419              * @event titlechange
69420              * Fires when the series title is changed via {@link #setTitle}.
69421              * @param {String} title The new title value
69422              * @param {Number} index The index in the collection of titles
69423              */
69424             titlechange: true
69425         });
69426
69427         me.mixins.observable.constructor.call(me, config);
69428
69429         me.on({
69430             scope: me,
69431             itemmouseover: me.onItemMouseOver,
69432             itemmouseout: me.onItemMouseOut,
69433             mouseleave: me.onMouseLeave
69434         });
69435     },
69436     
69437     /**
69438      * Iterate over each of the records for this series. The default implementation simply iterates
69439      * through the entire data store, but individual series implementations can override this to
69440      * provide custom handling, e.g. adding/removing records.
69441      * @param {Function} fn The function to execute for each record.
69442      * @param {Object} scope Scope for the fn.
69443      */
69444     eachRecord: function(fn, scope) {
69445         var chart = this.chart;
69446         (chart.substore || chart.store).each(fn, scope);
69447     },
69448
69449     /**
69450      * Return the number of records being displayed in this series. Defaults to the number of
69451      * records in the store; individual series implementations can override to provide custom handling.
69452      */
69453     getRecordCount: function() {
69454         var chart = this.chart,
69455             store = chart.substore || chart.store;
69456         return store ? store.getCount() : 0;
69457     },
69458
69459     /**
69460      * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
69461      * @param index
69462      */
69463     isExcluded: function(index) {
69464         var excludes = this.__excludes;
69465         return !!(excludes && excludes[index]);
69466     },
69467
69468     // @private set the bbox and clipBox for the series
69469     setBBox: function(noGutter) {
69470         var me = this,
69471             chart = me.chart,
69472             chartBBox = chart.chartBBox,
69473             gutterX = noGutter ? 0 : chart.maxGutter[0],
69474             gutterY = noGutter ? 0 : chart.maxGutter[1],
69475             clipBox, bbox;
69476
69477         clipBox = {
69478             x: chartBBox.x,
69479             y: chartBBox.y,
69480             width: chartBBox.width,
69481             height: chartBBox.height
69482         };
69483         me.clipBox = clipBox;
69484
69485         bbox = {
69486             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
69487             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
69488             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
69489             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
69490         };
69491         me.bbox = bbox;
69492     },
69493
69494     // @private set the animation for the sprite
69495     onAnimate: function(sprite, attr) {
69496         var me = this;
69497         sprite.stopAnimation();
69498         if (me.triggerAfterDraw) {
69499             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
69500         } else {
69501             me.triggerAfterDraw = true;
69502             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
69503                 listeners: {
69504                     'afteranimate': function() {
69505                         me.triggerAfterDraw = false;
69506                         me.fireEvent('afterrender');
69507                     }
69508                 }
69509             }));
69510         }
69511     },
69512
69513     // @private return the gutter.
69514     getGutters: function() {
69515         return [0, 0];
69516     },
69517
69518     // @private wrapper for the itemmouseover event.
69519     onItemMouseOver: function(item) {
69520         var me = this;
69521         if (item.series === me) {
69522             if (me.highlight) {
69523                 me.highlightItem(item);
69524             }
69525             if (me.tooltip) {
69526                 me.showTip(item);
69527             }
69528         }
69529     },
69530
69531     // @private wrapper for the itemmouseout event.
69532     onItemMouseOut: function(item) {
69533         var me = this;
69534         if (item.series === me) {
69535             me.unHighlightItem();
69536             if (me.tooltip) {
69537                 me.hideTip(item);
69538             }
69539         }
69540     },
69541
69542     // @private wrapper for the mouseleave event.
69543     onMouseLeave: function() {
69544         var me = this;
69545         me.unHighlightItem();
69546         if (me.tooltip) {
69547             me.hideTip();
69548         }
69549     },
69550
69551     /**
69552      * For a given x/y point relative to the Surface, find a corresponding item from this
69553      * series, if any.
69554      * @param {Number} x
69555      * @param {Number} y
69556      * @return {Object} An object describing the item, or null if there is no matching item.
69557      * The exact contents of this object will vary by series type, but should always contain the following:
69558      * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
69559      * @return {Object} return.value the value(s) of the item's data point
69560      * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
69561      * for this data item, which can be used as e.g. a tooltip anchor point.
69562      * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
69563      */
69564     getItemForPoint: function(x, y) {
69565         //if there are no items to query just return null.
69566         if (!this.items || !this.items.length || this.seriesIsHidden) {
69567             return null;
69568         }
69569         var me = this,
69570             items = me.items,
69571             bbox = me.bbox,
69572             item, i, ln;
69573         // Check bounds
69574         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
69575             return null;
69576         }
69577         for (i = 0, ln = items.length; i < ln; i++) {
69578             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
69579                 return items[i];
69580             }
69581         }
69582
69583         return null;
69584     },
69585
69586     isItemInPoint: function(x, y, item, i) {
69587         return false;
69588     },
69589
69590     /**
69591      * Hides all the elements in the series.
69592      */
69593     hideAll: function() {
69594         var me = this,
69595             items = me.items,
69596             item, len, i, j, l, sprite, shadows;
69597
69598         me.seriesIsHidden = true;
69599         me._prevShowMarkers = me.showMarkers;
69600
69601         me.showMarkers = false;
69602         //hide all labels
69603         me.hideLabels(0);
69604         //hide all sprites
69605         for (i = 0, len = items.length; i < len; i++) {
69606             item = items[i];
69607             sprite = item.sprite;
69608             if (sprite) {
69609                 sprite.setAttributes({
69610                     hidden: true
69611                 }, true);
69612             }
69613
69614             if (sprite && sprite.shadows) {
69615                 shadows = sprite.shadows;
69616                 for (j = 0, l = shadows.length; j < l; ++j) {
69617                     shadows[j].setAttributes({
69618                         hidden: true
69619                     }, true);
69620                 }
69621             }
69622         }
69623     },
69624
69625     /**
69626      * Shows all the elements in the series.
69627      */
69628     showAll: function() {
69629         var me = this,
69630             prevAnimate = me.chart.animate;
69631         me.chart.animate = false;
69632         me.seriesIsHidden = false;
69633         me.showMarkers = me._prevShowMarkers;
69634         me.drawSeries();
69635         me.chart.animate = prevAnimate;
69636     },
69637
69638     /**
69639      * Returns a string with the color to be used for the series legend item.
69640      */
69641     getLegendColor: function(index) {
69642         var me = this, fill, stroke;
69643         if (me.seriesStyle) {
69644             fill = me.seriesStyle.fill;
69645             stroke = me.seriesStyle.stroke;
69646             if (fill && fill != 'none') {
69647                 return fill;
69648             }
69649             return stroke;
69650         }
69651         return '#000';
69652     },
69653
69654     /**
69655      * Checks whether the data field should be visible in the legend
69656      * @private
69657      * @param {Number} index The index of the current item
69658      */
69659     visibleInLegend: function(index){
69660         var excludes = this.__excludes;
69661         if (excludes) {
69662             return !excludes[index];
69663         }
69664         return !this.seriesIsHidden;
69665     },
69666
69667     /**
69668      * Changes the value of the {@link #title} for the series.
69669      * Arguments can take two forms:
69670      * <ul>
69671      * <li>A single String value: this will be used as the new single title for the series (applies
69672      * to series with only one yField)</li>
69673      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
69674      * </ul>
69675      * @param {Number} index
69676      * @param {String} title
69677      */
69678     setTitle: function(index, title) {
69679         var me = this,
69680             oldTitle = me.title;
69681
69682         if (Ext.isString(index)) {
69683             title = index;
69684             index = 0;
69685         }
69686
69687         if (Ext.isArray(oldTitle)) {
69688             oldTitle[index] = title;
69689         } else {
69690             me.title = title;
69691         }
69692
69693         me.fireEvent('titlechange', title, index);
69694     }
69695 });
69696
69697 /**
69698  * @class Ext.chart.series.Cartesian
69699  * @extends Ext.chart.series.Series
69700  *
69701  * Common base class for series implementations which plot values using x/y coordinates.
69702  */
69703 Ext.define('Ext.chart.series.Cartesian', {
69704
69705     /* Begin Definitions */
69706
69707     extend: 'Ext.chart.series.Series',
69708
69709     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
69710
69711     /* End Definitions */
69712
69713     /**
69714      * The field used to access the x axis value from the items from the data
69715      * source.
69716      *
69717      * @cfg xField
69718      * @type String
69719      */
69720     xField: null,
69721
69722     /**
69723      * The field used to access the y-axis value from the items from the data
69724      * source.
69725      *
69726      * @cfg yField
69727      * @type String
69728      */
69729     yField: null,
69730
69731     /**
69732      * @cfg {String} axis
69733      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
69734      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
69735      * relative scale will be used.
69736      */
69737     axis: 'left',
69738
69739     getLegendLabels: function() {
69740         var me = this,
69741             labels = [],
69742             combinations = me.combinations;
69743
69744         Ext.each([].concat(me.yField), function(yField, i) {
69745             var title = me.title;
69746             // Use the 'title' config if present, otherwise use the raw yField name
69747             labels.push((Ext.isArray(title) ? title[i] : title) || yField);
69748         });
69749
69750         // Handle yFields combined via legend drag-drop
69751         if (combinations) {
69752             Ext.each(combinations, function(combo) {
69753                 var label0 = labels[combo[0]],
69754                     label1 = labels[combo[1]];
69755                 labels[combo[1]] = label0 + ' & ' + label1;
69756                 labels.splice(combo[0], 1);
69757             });
69758         }
69759
69760         return labels;
69761     },
69762
69763     /**
69764      * @protected Iterates over a given record's values for each of this series's yFields,
69765      * executing a given function for each value. Any yFields that have been combined
69766      * via legend drag-drop will be treated as a single value.
69767      * @param {Ext.data.Model} record
69768      * @param {Function} fn
69769      * @param {Object} scope
69770      */
69771     eachYValue: function(record, fn, scope) {
69772         Ext.each(this.getYValueAccessors(), function(accessor, i) {
69773             fn.call(scope, accessor(record), i);
69774         });
69775     },
69776
69777     /**
69778      * @protected Returns the number of yField values, taking into account fields combined
69779      * via legend drag-drop.
69780      * @return {Number}
69781      */
69782     getYValueCount: function() {
69783         return this.getYValueAccessors().length;
69784     },
69785
69786     combine: function(index1, index2) {
69787         var me = this,
69788             accessors = me.getYValueAccessors(),
69789             accessor1 = accessors[index1],
69790             accessor2 = accessors[index2];
69791
69792         // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
69793         accessors[index2] = function(record) {
69794             return accessor1(record) + accessor2(record);
69795         };
69796         accessors.splice(index1, 1);
69797
69798         me.callParent([index1, index2]);
69799     },
69800
69801     clearCombinations: function() {
69802         // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
69803         delete this.yValueAccessors;
69804         this.callParent();
69805     },
69806
69807     /**
69808      * @protected Returns an array of functions, each of which returns the value of the yField
69809      * corresponding to function's index in the array, for a given record (each function takes the
69810      * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
69811      * this list of accessors will be kept in sync with those combinations.
69812      * @return {Array} array of accessor functions
69813      */
69814     getYValueAccessors: function() {
69815         var me = this,
69816             accessors = me.yValueAccessors;
69817         if (!accessors) {
69818             accessors = me.yValueAccessors = [];
69819             Ext.each([].concat(me.yField), function(yField) {
69820                 accessors.push(function(record) {
69821                     return record.get(yField);
69822                 });
69823             });
69824         }
69825         return accessors;
69826     },
69827
69828     /**
69829      * Calculate the min and max values for this series's xField.
69830      * @return {Array} [min, max]
69831      */
69832     getMinMaxXValues: function() {
69833         var me = this,
69834             min, max,
69835             xField = me.xField;
69836
69837         if (me.getRecordCount() > 0) {
69838             min = Infinity;
69839             max = -min;
69840             me.eachRecord(function(record) {
69841                 var xValue = record.get(xField);
69842                 if (xValue > max) {
69843                     max = xValue;
69844                 }
69845                 if (xValue < min) {
69846                     min = xValue;
69847                 }
69848             });
69849         } else {
69850             min = max = 0;
69851         }
69852         return [min, max];
69853     },
69854
69855     /**
69856      * Calculate the min and max values for this series's yField(s). Takes into account yField
69857      * combinations, exclusions, and stacking.
69858      * @return {Array} [min, max]
69859      */
69860     getMinMaxYValues: function() {
69861         var me = this,
69862             stacked = me.stacked,
69863             min, max,
69864             positiveTotal, negativeTotal;
69865
69866         function eachYValueStacked(yValue, i) {
69867             if (!me.isExcluded(i)) {
69868                 if (yValue < 0) {
69869                     negativeTotal += yValue;
69870                 } else {
69871                     positiveTotal += yValue;
69872                 }
69873             }
69874         }
69875
69876         function eachYValue(yValue, i) {
69877             if (!me.isExcluded(i)) {
69878                 if (yValue > max) {
69879                     max = yValue;
69880                 }
69881                 if (yValue < min) {
69882                     min = yValue;
69883                 }
69884             }
69885         }
69886
69887         if (me.getRecordCount() > 0) {
69888             min = Infinity;
69889             max = -min;
69890             me.eachRecord(function(record) {
69891                 if (stacked) {
69892                     positiveTotal = 0;
69893                     negativeTotal = 0;
69894                     me.eachYValue(record, eachYValueStacked);
69895                     if (positiveTotal > max) {
69896                         max = positiveTotal;
69897                     }
69898                     if (negativeTotal < min) {
69899                         min = negativeTotal;
69900                     }
69901                 } else {
69902                     me.eachYValue(record, eachYValue);
69903                 }
69904             });
69905         } else {
69906             min = max = 0;
69907         }
69908         return [min, max];
69909     },
69910
69911     getAxesForXAndYFields: function() {
69912         var me = this,
69913             axes = me.chart.axes,
69914             axis = [].concat(me.axis),
69915             xAxis, yAxis;
69916
69917         if (Ext.Array.indexOf(axis, 'top') > -1) {
69918             xAxis = 'top';
69919         } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
69920             xAxis = 'bottom';
69921         } else {
69922             if (axes.get('top')) {
69923                 xAxis = 'top';
69924             } else if (axes.get('bottom')) {
69925                 xAxis = 'bottom';
69926             }
69927         }
69928
69929         if (Ext.Array.indexOf(axis, 'left') > -1) {
69930             yAxis = 'left';
69931         } else if (Ext.Array.indexOf(axis, 'right') > -1) {
69932             yAxis = 'right';
69933         } else {
69934             if (axes.get('left')) {
69935                 yAxis = 'left';
69936             } else if (axes.get('right')) {
69937                 yAxis = 'right';
69938             }
69939         }
69940
69941         return {
69942             xAxis: xAxis,
69943             yAxis: yAxis
69944         };
69945     }
69946
69947
69948 });
69949
69950 /**
69951  * @class Ext.chart.series.Area
69952  * @extends Ext.chart.series.Cartesian
69953  *
69954  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
69955  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
69956  * documentation for more information. A typical configuration object for the area series could be:
69957  *
69958  *     @example
69959  *     var store = Ext.create('Ext.data.JsonStore', {
69960  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69961  *         data: [
69962  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
69963  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
69964  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
69965  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
69966  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
69967  *         ]
69968  *     });
69969  *
69970  *     Ext.create('Ext.chart.Chart', {
69971  *         renderTo: Ext.getBody(),
69972  *         width: 500,
69973  *         height: 300,
69974  *         store: store,
69975  *         axes: [
69976  *             {
69977  *                 type: 'Numeric',
69978  *                 grid: true,
69979  *                 position: 'left',
69980  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
69981  *                 title: 'Sample Values',
69982  *                 grid: {
69983  *                     odd: {
69984  *                         opacity: 1,
69985  *                         fill: '#ddd',
69986  *                         stroke: '#bbb',
69987  *                         'stroke-width': 1
69988  *                     }
69989  *                 },
69990  *                 minimum: 0,
69991  *                 adjustMinimumByMajorUnit: 0
69992  *             },
69993  *             {
69994  *                 type: 'Category',
69995  *                 position: 'bottom',
69996  *                 fields: ['name'],
69997  *                 title: 'Sample Metrics',
69998  *                 grid: true,
69999  *                 label: {
70000  *                     rotate: {
70001  *                         degrees: 315
70002  *                     }
70003  *                 }
70004  *             }
70005  *         ],
70006  *         series: [{
70007  *             type: 'area',
70008  *             highlight: false,
70009  *             axis: 'left',
70010  *             xField: 'name',
70011  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70012  *             style: {
70013  *                 opacity: 0.93
70014  *             }
70015  *         }]
70016  *     });
70017  *
70018  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70019  * 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,
70020  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70021  * to the style object.
70022  *
70023  * @xtype area
70024  */
70025 Ext.define('Ext.chart.series.Area', {
70026
70027     /* Begin Definitions */
70028
70029     extend: 'Ext.chart.series.Cartesian',
70030
70031     alias: 'series.area',
70032
70033     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70034
70035     /* End Definitions */
70036
70037     type: 'area',
70038
70039     // @private Area charts are alyways stacked
70040     stacked: true,
70041
70042     /**
70043      * @cfg {Object} style
70044      * Append styling properties to this object for it to override theme properties.
70045      */
70046     style: {},
70047
70048     constructor: function(config) {
70049         this.callParent(arguments);
70050         var me = this,
70051             surface = me.chart.surface,
70052             i, l;
70053         Ext.apply(me, config, {
70054             __excludes: [],
70055             highlightCfg: {
70056                 lineWidth: 3,
70057                 stroke: '#55c',
70058                 opacity: 0.8,
70059                 color: '#f00'
70060             }
70061         });
70062         if (me.highlight) {
70063             me.highlightSprite = surface.add({
70064                 type: 'path',
70065                 path: ['M', 0, 0],
70066                 zIndex: 1000,
70067                 opacity: 0.3,
70068                 lineWidth: 5,
70069                 hidden: true,
70070                 stroke: '#444'
70071             });
70072         }
70073         me.group = surface.getGroup(me.seriesId);
70074     },
70075
70076     // @private Shrinks dataSets down to a smaller size
70077     shrink: function(xValues, yValues, size) {
70078         var len = xValues.length,
70079             ratio = Math.floor(len / size),
70080             i, j,
70081             xSum = 0,
70082             yCompLen = this.areas.length,
70083             ySum = [],
70084             xRes = [],
70085             yRes = [];
70086         //initialize array
70087         for (j = 0; j < yCompLen; ++j) {
70088             ySum[j] = 0;
70089         }
70090         for (i = 0; i < len; ++i) {
70091             xSum += xValues[i];
70092             for (j = 0; j < yCompLen; ++j) {
70093                 ySum[j] += yValues[i][j];
70094             }
70095             if (i % ratio == 0) {
70096                 //push averages
70097                 xRes.push(xSum/ratio);
70098                 for (j = 0; j < yCompLen; ++j) {
70099                     ySum[j] /= ratio;
70100                 }
70101                 yRes.push(ySum);
70102                 //reset sum accumulators
70103                 xSum = 0;
70104                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70105                     ySum[j] = 0;
70106                 }
70107             }
70108         }
70109         return {
70110             x: xRes,
70111             y: yRes
70112         };
70113     },
70114
70115     // @private Get chart and data boundaries
70116     getBounds: function() {
70117         var me = this,
70118             chart = me.chart,
70119             store = chart.getChartStore(),
70120             areas = [].concat(me.yField),
70121             areasLen = areas.length,
70122             xValues = [],
70123             yValues = [],
70124             infinity = Infinity,
70125             minX = infinity,
70126             minY = infinity,
70127             maxX = -infinity,
70128             maxY = -infinity,
70129             math = Math,
70130             mmin = math.min,
70131             mmax = math.max,
70132             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70133
70134         me.setBBox();
70135         bbox = me.bbox;
70136
70137         // Run through the axis
70138         if (me.axis) {
70139             axis = chart.axes.get(me.axis);
70140             if (axis) {
70141                 out = axis.calcEnds();
70142                 minY = out.from || axis.prevMin;
70143                 maxY = mmax(out.to || axis.prevMax, 0);
70144             }
70145         }
70146
70147         if (me.yField && !Ext.isNumber(minY)) {
70148             axis = Ext.create('Ext.chart.axis.Axis', {
70149                 chart: chart,
70150                 fields: [].concat(me.yField)
70151             });
70152             out = axis.calcEnds();
70153             minY = out.from || axis.prevMin;
70154             maxY = mmax(out.to || axis.prevMax, 0);
70155         }
70156
70157         if (!Ext.isNumber(minY)) {
70158             minY = 0;
70159         }
70160         if (!Ext.isNumber(maxY)) {
70161             maxY = 0;
70162         }
70163
70164         store.each(function(record, i) {
70165             xValue = record.get(me.xField);
70166             yValue = [];
70167             if (typeof xValue != 'number') {
70168                 xValue = i;
70169             }
70170             xValues.push(xValue);
70171             acumY = 0;
70172             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70173                 areaElem = record.get(areas[areaIndex]);
70174                 if (typeof areaElem == 'number') {
70175                     minY = mmin(minY, areaElem);
70176                     yValue.push(areaElem);
70177                     acumY += areaElem;
70178                 }
70179             }
70180             minX = mmin(minX, xValue);
70181             maxX = mmax(maxX, xValue);
70182             maxY = mmax(maxY, acumY);
70183             yValues.push(yValue);
70184         }, me);
70185
70186         xScale = bbox.width / ((maxX - minX) || 1);
70187         yScale = bbox.height / ((maxY - minY) || 1);
70188
70189         ln = xValues.length;
70190         if ((ln > bbox.width) && me.areas) {
70191             sumValues = me.shrink(xValues, yValues, bbox.width);
70192             xValues = sumValues.x;
70193             yValues = sumValues.y;
70194         }
70195
70196         return {
70197             bbox: bbox,
70198             minX: minX,
70199             minY: minY,
70200             xValues: xValues,
70201             yValues: yValues,
70202             xScale: xScale,
70203             yScale: yScale,
70204             areasLen: areasLen
70205         };
70206     },
70207
70208     // @private Build an array of paths for the chart
70209     getPaths: function() {
70210         var me = this,
70211             chart = me.chart,
70212             store = chart.getChartStore(),
70213             first = true,
70214             bounds = me.getBounds(),
70215             bbox = bounds.bbox,
70216             items = me.items = [],
70217             componentPaths = [],
70218             componentPath,
70219             paths = [],
70220             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70221
70222         ln = bounds.xValues.length;
70223         // Start the path
70224         for (i = 0; i < ln; i++) {
70225             xValue = bounds.xValues[i];
70226             yValue = bounds.yValues[i];
70227             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70228             acumY = 0;
70229             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70230                 // Excluded series
70231                 if (me.__excludes[areaIndex]) {
70232                     continue;
70233                 }
70234                 if (!componentPaths[areaIndex]) {
70235                     componentPaths[areaIndex] = [];
70236                 }
70237                 areaElem = yValue[areaIndex];
70238                 acumY += areaElem;
70239                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70240                 if (!paths[areaIndex]) {
70241                     paths[areaIndex] = ['M', x, y];
70242                     componentPaths[areaIndex].push(['L', x, y]);
70243                 } else {
70244                     paths[areaIndex].push('L', x, y);
70245                     componentPaths[areaIndex].push(['L', x, y]);
70246                 }
70247                 if (!items[areaIndex]) {
70248                     items[areaIndex] = {
70249                         pointsUp: [],
70250                         pointsDown: [],
70251                         series: me
70252                     };
70253                 }
70254                 items[areaIndex].pointsUp.push([x, y]);
70255             }
70256         }
70257
70258         // Close the paths
70259         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70260             // Excluded series
70261             if (me.__excludes[areaIndex]) {
70262                 continue;
70263             }
70264             path = paths[areaIndex];
70265             // Close bottom path to the axis
70266             if (areaIndex == 0 || first) {
70267                 first = false;
70268                 path.push('L', x, bbox.y + bbox.height,
70269                           'L', bbox.x, bbox.y + bbox.height,
70270                           'Z');
70271             }
70272             // Close other paths to the one before them
70273             else {
70274                 componentPath = componentPaths[prevAreaIndex];
70275                 componentPath.reverse();
70276                 path.push('L', x, componentPath[0][2]);
70277                 for (i = 0; i < ln; i++) {
70278                     path.push(componentPath[i][0],
70279                               componentPath[i][1],
70280                               componentPath[i][2]);
70281                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
70282                 }
70283                 path.push('L', bbox.x, path[2], 'Z');
70284             }
70285             prevAreaIndex = areaIndex;
70286         }
70287         return {
70288             paths: paths,
70289             areasLen: bounds.areasLen
70290         };
70291     },
70292
70293     /**
70294      * Draws the series for the current chart.
70295      */
70296     drawSeries: function() {
70297         var me = this,
70298             chart = me.chart,
70299             store = chart.getChartStore(),
70300             surface = chart.surface,
70301             animate = chart.animate,
70302             group = me.group,
70303             endLineStyle = Ext.apply(me.seriesStyle, me.style),
70304             colorArrayStyle = me.colorArrayStyle,
70305             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
70306             areaIndex, areaElem, paths, path, rendererAttributes;
70307
70308         me.unHighlightItem();
70309         me.cleanHighlights();
70310
70311         if (!store || !store.getCount()) {
70312             return;
70313         }
70314
70315         paths = me.getPaths();
70316
70317         if (!me.areas) {
70318             me.areas = [];
70319         }
70320
70321         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
70322             // Excluded series
70323             if (me.__excludes[areaIndex]) {
70324                 continue;
70325             }
70326             if (!me.areas[areaIndex]) {
70327                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
70328                     type: 'path',
70329                     group: group,
70330                     // 'clip-rect': me.clipBox,
70331                     path: paths.paths[areaIndex],
70332                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
70333                     fill: colorArrayStyle[areaIndex % colorArrayLength]
70334                 }, endLineStyle || {}));
70335             }
70336             areaElem = me.areas[areaIndex];
70337             path = paths.paths[areaIndex];
70338             if (animate) {
70339                 //Add renderer to line. There is not a unique record associated with this.
70340                 rendererAttributes = me.renderer(areaElem, false, {
70341                     path: path,
70342                     // 'clip-rect': me.clipBox,
70343                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70344                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70345                 }, areaIndex, store);
70346                 //fill should not be used here but when drawing the special fill path object
70347                 me.animation = me.onAnimate(areaElem, {
70348                     to: rendererAttributes
70349                 });
70350             } else {
70351                 rendererAttributes = me.renderer(areaElem, false, {
70352                     path: path,
70353                     // 'clip-rect': me.clipBox,
70354                     hidden: false,
70355                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70356                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70357                 }, areaIndex, store);
70358                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
70359             }
70360         }
70361         me.renderLabels();
70362         me.renderCallouts();
70363     },
70364
70365     // @private
70366     onAnimate: function(sprite, attr) {
70367         sprite.show();
70368         return this.callParent(arguments);
70369     },
70370
70371     // @private
70372     onCreateLabel: function(storeItem, item, i, display) {
70373         var me = this,
70374             group = me.labelsGroup,
70375             config = me.label,
70376             bbox = me.bbox,
70377             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
70378
70379         return me.chart.surface.add(Ext.apply({
70380             'type': 'text',
70381             'text-anchor': 'middle',
70382             'group': group,
70383             'x': item.point[0],
70384             'y': bbox.y + bbox.height / 2
70385         }, endLabelStyle || {}));
70386     },
70387
70388     // @private
70389     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
70390         var me = this,
70391             chart = me.chart,
70392             resizing = chart.resizing,
70393             config = me.label,
70394             format = config.renderer,
70395             field = config.field,
70396             bbox = me.bbox,
70397             x = item.point[0],
70398             y = item.point[1],
70399             bb, width, height;
70400
70401         label.setAttributes({
70402             text: format(storeItem.get(field[index])),
70403             hidden: true
70404         }, true);
70405
70406         bb = label.getBBox();
70407         width = bb.width / 2;
70408         height = bb.height / 2;
70409
70410         x = x - width < bbox.x? bbox.x + width : x;
70411         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
70412         y = y - height < bbox.y? bbox.y + height : y;
70413         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
70414
70415         if (me.chart.animate && !me.chart.resizing) {
70416             label.show(true);
70417             me.onAnimate(label, {
70418                 to: {
70419                     x: x,
70420                     y: y
70421                 }
70422             });
70423         } else {
70424             label.setAttributes({
70425                 x: x,
70426                 y: y
70427             }, true);
70428             if (resizing) {
70429                 me.animation.on('afteranimate', function() {
70430                     label.show(true);
70431                 });
70432             } else {
70433                 label.show(true);
70434             }
70435         }
70436     },
70437
70438     // @private
70439     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
70440         var me = this,
70441             chart = me.chart,
70442             surface = chart.surface,
70443             resizing = chart.resizing,
70444             config = me.callouts,
70445             items = me.items,
70446             prev = (i == 0) ? false : items[i -1].point,
70447             next = (i == items.length -1) ? false : items[i +1].point,
70448             cur = item.point,
70449             dir, norm, normal, a, aprev, anext,
70450             bbox = callout.label.getBBox(),
70451             offsetFromViz = 30,
70452             offsetToSide = 10,
70453             offsetBox = 3,
70454             boxx, boxy, boxw, boxh,
70455             p, clipRect = me.clipRect,
70456             x, y;
70457
70458         //get the right two points
70459         if (!prev) {
70460             prev = cur;
70461         }
70462         if (!next) {
70463             next = cur;
70464         }
70465         a = (next[1] - prev[1]) / (next[0] - prev[0]);
70466         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
70467         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
70468
70469         norm = Math.sqrt(1 + a * a);
70470         dir = [1 / norm, a / norm];
70471         normal = [-dir[1], dir[0]];
70472
70473         //keep the label always on the outer part of the "elbow"
70474         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
70475             normal[0] *= -1;
70476             normal[1] *= -1;
70477         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
70478             normal[0] *= -1;
70479             normal[1] *= -1;
70480         }
70481
70482         //position
70483         x = cur[0] + normal[0] * offsetFromViz;
70484         y = cur[1] + normal[1] * offsetFromViz;
70485
70486         //box position and dimensions
70487         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70488         boxy = y - bbox.height /2 - offsetBox;
70489         boxw = bbox.width + 2 * offsetBox;
70490         boxh = bbox.height + 2 * offsetBox;
70491
70492         //now check if we're out of bounds and invert the normal vector correspondingly
70493         //this may add new overlaps between labels (but labels won't be out of bounds).
70494         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
70495             normal[0] *= -1;
70496         }
70497         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
70498             normal[1] *= -1;
70499         }
70500
70501         //update positions
70502         x = cur[0] + normal[0] * offsetFromViz;
70503         y = cur[1] + normal[1] * offsetFromViz;
70504
70505         //update box position and dimensions
70506         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70507         boxy = y - bbox.height /2 - offsetBox;
70508         boxw = bbox.width + 2 * offsetBox;
70509         boxh = bbox.height + 2 * offsetBox;
70510
70511         //set the line from the middle of the pie to the box.
70512         callout.lines.setAttributes({
70513             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70514         }, true);
70515         //set box position
70516         callout.box.setAttributes({
70517             x: boxx,
70518             y: boxy,
70519             width: boxw,
70520             height: boxh
70521         }, true);
70522         //set text position
70523         callout.label.setAttributes({
70524             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70525             y: y
70526         }, true);
70527         for (p in callout) {
70528             callout[p].show(true);
70529         }
70530     },
70531
70532     isItemInPoint: function(x, y, item, i) {
70533         var me = this,
70534             pointsUp = item.pointsUp,
70535             pointsDown = item.pointsDown,
70536             abs = Math.abs,
70537             dist = Infinity, p, pln, point;
70538
70539         for (p = 0, pln = pointsUp.length; p < pln; p++) {
70540             point = [pointsUp[p][0], pointsUp[p][1]];
70541             if (dist > abs(x - point[0])) {
70542                 dist = abs(x - point[0]);
70543             } else {
70544                 point = pointsUp[p -1];
70545                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
70546                     item.storeIndex = p -1;
70547                     item.storeField = me.yField[i];
70548                     item.storeItem = me.chart.store.getAt(p -1);
70549                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
70550                     return true;
70551                 } else {
70552                     break;
70553                 }
70554             }
70555         }
70556         return false;
70557     },
70558
70559     /**
70560      * Highlight this entire series.
70561      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70562      */
70563     highlightSeries: function() {
70564         var area, to, fillColor;
70565         if (this._index !== undefined) {
70566             area = this.areas[this._index];
70567             if (area.__highlightAnim) {
70568                 area.__highlightAnim.paused = true;
70569             }
70570             area.__highlighted = true;
70571             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
70572             area.__prevFill = area.__prevFill || area.attr.fill;
70573             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
70574             fillColor = Ext.draw.Color.fromString(area.__prevFill);
70575             to = {
70576                 lineWidth: (area.__prevLineWidth || 0) + 2
70577             };
70578             if (fillColor) {
70579                 to.fill = fillColor.getLighter(0.2).toString();
70580             }
70581             else {
70582                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
70583             }
70584             if (this.chart.animate) {
70585                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
70586                     target: area,
70587                     to: to
70588                 }, this.chart.animate));
70589             }
70590             else {
70591                 area.setAttributes(to, true);
70592             }
70593         }
70594     },
70595
70596     /**
70597      * UnHighlight this entire series.
70598      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70599      */
70600     unHighlightSeries: function() {
70601         var area;
70602         if (this._index !== undefined) {
70603             area = this.areas[this._index];
70604             if (area.__highlightAnim) {
70605                 area.__highlightAnim.paused = true;
70606             }
70607             if (area.__highlighted) {
70608                 area.__highlighted = false;
70609                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
70610                     target: area,
70611                     to: {
70612                         fill: area.__prevFill,
70613                         opacity: area.__prevOpacity,
70614                         lineWidth: area.__prevLineWidth
70615                     }
70616                 });
70617             }
70618         }
70619     },
70620
70621     /**
70622      * Highlight the specified item. If no item is provided the whole series will be highlighted.
70623      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70624      */
70625     highlightItem: function(item) {
70626         var me = this,
70627             points, path;
70628         if (!item) {
70629             this.highlightSeries();
70630             return;
70631         }
70632         points = item._points;
70633         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
70634                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
70635         me.highlightSprite.setAttributes({
70636             path: path,
70637             hidden: false
70638         }, true);
70639     },
70640
70641     /**
70642      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
70643      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
70644      */
70645     unHighlightItem: function(item) {
70646         if (!item) {
70647             this.unHighlightSeries();
70648         }
70649
70650         if (this.highlightSprite) {
70651             this.highlightSprite.hide(true);
70652         }
70653     },
70654
70655     // @private
70656     hideAll: function() {
70657         if (!isNaN(this._index)) {
70658             this.__excludes[this._index] = true;
70659             this.areas[this._index].hide(true);
70660             this.drawSeries();
70661         }
70662     },
70663
70664     // @private
70665     showAll: function() {
70666         if (!isNaN(this._index)) {
70667             this.__excludes[this._index] = false;
70668             this.areas[this._index].show(true);
70669             this.drawSeries();
70670         }
70671     },
70672
70673     /**
70674      * Returns the color of the series (to be displayed as color for the series legend item).
70675      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70676      */
70677     getLegendColor: function(index) {
70678         var me = this;
70679         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70680     }
70681 });
70682 /**
70683  * @class Ext.chart.series.Area
70684  * @extends Ext.chart.series.Cartesian
70685  *
70686  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
70687  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
70688  * documentation for more information. A typical configuration object for the area series could be:
70689  *
70690  *     @example
70691  *     var store = Ext.create('Ext.data.JsonStore', {
70692  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70693  *         data: [
70694  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70695  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70696  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70697  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70698  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70699  *         ]
70700  *     });
70701  *
70702  *     Ext.create('Ext.chart.Chart', {
70703  *         renderTo: Ext.getBody(),
70704  *         width: 500,
70705  *         height: 300,
70706  *         store: store,
70707  *         axes: [
70708  *             {
70709  *                 type: 'Numeric',
70710  *                 grid: true,
70711  *                 position: 'left',
70712  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
70713  *                 title: 'Sample Values',
70714  *                 grid: {
70715  *                     odd: {
70716  *                         opacity: 1,
70717  *                         fill: '#ddd',
70718  *                         stroke: '#bbb',
70719  *                         'stroke-width': 1
70720  *                     }
70721  *                 },
70722  *                 minimum: 0,
70723  *                 adjustMinimumByMajorUnit: 0
70724  *             },
70725  *             {
70726  *                 type: 'Category',
70727  *                 position: 'bottom',
70728  *                 fields: ['name'],
70729  *                 title: 'Sample Metrics',
70730  *                 grid: true,
70731  *                 label: {
70732  *                     rotate: {
70733  *                         degrees: 315
70734  *                     }
70735  *                 }
70736  *             }
70737  *         ],
70738  *         series: [{
70739  *             type: 'area',
70740  *             highlight: false,
70741  *             axis: 'left',
70742  *             xField: 'name',
70743  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70744  *             style: {
70745  *                 opacity: 0.93
70746  *             }
70747  *         }]
70748  *     });
70749  *
70750  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70751  * 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,
70752  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70753  * to the style object.
70754  *
70755  * @xtype area
70756  */
70757 Ext.define('Ext.chart.series.Area', {
70758
70759     /* Begin Definitions */
70760
70761     extend: 'Ext.chart.series.Cartesian',
70762
70763     alias: 'series.area',
70764
70765     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70766
70767     /* End Definitions */
70768
70769     type: 'area',
70770
70771     // @private Area charts are alyways stacked
70772     stacked: true,
70773
70774     /**
70775      * @cfg {Object} style
70776      * Append styling properties to this object for it to override theme properties.
70777      */
70778     style: {},
70779
70780     constructor: function(config) {
70781         this.callParent(arguments);
70782         var me = this,
70783             surface = me.chart.surface,
70784             i, l;
70785         Ext.apply(me, config, {
70786             __excludes: [],
70787             highlightCfg: {
70788                 lineWidth: 3,
70789                 stroke: '#55c',
70790                 opacity: 0.8,
70791                 color: '#f00'
70792             }
70793         });
70794         if (me.highlight) {
70795             me.highlightSprite = surface.add({
70796                 type: 'path',
70797                 path: ['M', 0, 0],
70798                 zIndex: 1000,
70799                 opacity: 0.3,
70800                 lineWidth: 5,
70801                 hidden: true,
70802                 stroke: '#444'
70803             });
70804         }
70805         me.group = surface.getGroup(me.seriesId);
70806     },
70807
70808     // @private Shrinks dataSets down to a smaller size
70809     shrink: function(xValues, yValues, size) {
70810         var len = xValues.length,
70811             ratio = Math.floor(len / size),
70812             i, j,
70813             xSum = 0,
70814             yCompLen = this.areas.length,
70815             ySum = [],
70816             xRes = [],
70817             yRes = [];
70818         //initialize array
70819         for (j = 0; j < yCompLen; ++j) {
70820             ySum[j] = 0;
70821         }
70822         for (i = 0; i < len; ++i) {
70823             xSum += xValues[i];
70824             for (j = 0; j < yCompLen; ++j) {
70825                 ySum[j] += yValues[i][j];
70826             }
70827             if (i % ratio == 0) {
70828                 //push averages
70829                 xRes.push(xSum/ratio);
70830                 for (j = 0; j < yCompLen; ++j) {
70831                     ySum[j] /= ratio;
70832                 }
70833                 yRes.push(ySum);
70834                 //reset sum accumulators
70835                 xSum = 0;
70836                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70837                     ySum[j] = 0;
70838                 }
70839             }
70840         }
70841         return {
70842             x: xRes,
70843             y: yRes
70844         };
70845     },
70846
70847     // @private Get chart and data boundaries
70848     getBounds: function() {
70849         var me = this,
70850             chart = me.chart,
70851             store = chart.getChartStore(),
70852             areas = [].concat(me.yField),
70853             areasLen = areas.length,
70854             xValues = [],
70855             yValues = [],
70856             infinity = Infinity,
70857             minX = infinity,
70858             minY = infinity,
70859             maxX = -infinity,
70860             maxY = -infinity,
70861             math = Math,
70862             mmin = math.min,
70863             mmax = math.max,
70864             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70865
70866         me.setBBox();
70867         bbox = me.bbox;
70868
70869         // Run through the axis
70870         if (me.axis) {
70871             axis = chart.axes.get(me.axis);
70872             if (axis) {
70873                 out = axis.calcEnds();
70874                 minY = out.from || axis.prevMin;
70875                 maxY = mmax(out.to || axis.prevMax, 0);
70876             }
70877         }
70878
70879         if (me.yField && !Ext.isNumber(minY)) {
70880             axis = Ext.create('Ext.chart.axis.Axis', {
70881                 chart: chart,
70882                 fields: [].concat(me.yField)
70883             });
70884             out = axis.calcEnds();
70885             minY = out.from || axis.prevMin;
70886             maxY = mmax(out.to || axis.prevMax, 0);
70887         }
70888
70889         if (!Ext.isNumber(minY)) {
70890             minY = 0;
70891         }
70892         if (!Ext.isNumber(maxY)) {
70893             maxY = 0;
70894         }
70895
70896         store.each(function(record, i) {
70897             xValue = record.get(me.xField);
70898             yValue = [];
70899             if (typeof xValue != 'number') {
70900                 xValue = i;
70901             }
70902             xValues.push(xValue);
70903             acumY = 0;
70904             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70905                 areaElem = record.get(areas[areaIndex]);
70906                 if (typeof areaElem == 'number') {
70907                     minY = mmin(minY, areaElem);
70908                     yValue.push(areaElem);
70909                     acumY += areaElem;
70910                 }
70911             }
70912             minX = mmin(minX, xValue);
70913             maxX = mmax(maxX, xValue);
70914             maxY = mmax(maxY, acumY);
70915             yValues.push(yValue);
70916         }, me);
70917
70918         xScale = bbox.width / ((maxX - minX) || 1);
70919         yScale = bbox.height / ((maxY - minY) || 1);
70920
70921         ln = xValues.length;
70922         if ((ln > bbox.width) && me.areas) {
70923             sumValues = me.shrink(xValues, yValues, bbox.width);
70924             xValues = sumValues.x;
70925             yValues = sumValues.y;
70926         }
70927
70928         return {
70929             bbox: bbox,
70930             minX: minX,
70931             minY: minY,
70932             xValues: xValues,
70933             yValues: yValues,
70934             xScale: xScale,
70935             yScale: yScale,
70936             areasLen: areasLen
70937         };
70938     },
70939
70940     // @private Build an array of paths for the chart
70941     getPaths: function() {
70942         var me = this,
70943             chart = me.chart,
70944             store = chart.getChartStore(),
70945             first = true,
70946             bounds = me.getBounds(),
70947             bbox = bounds.bbox,
70948             items = me.items = [],
70949             componentPaths = [],
70950             componentPath,
70951             paths = [],
70952             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70953
70954         ln = bounds.xValues.length;
70955         // Start the path
70956         for (i = 0; i < ln; i++) {
70957             xValue = bounds.xValues[i];
70958             yValue = bounds.yValues[i];
70959             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70960             acumY = 0;
70961             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70962                 // Excluded series
70963                 if (me.__excludes[areaIndex]) {
70964                     continue;
70965                 }
70966                 if (!componentPaths[areaIndex]) {
70967                     componentPaths[areaIndex] = [];
70968                 }
70969                 areaElem = yValue[areaIndex];
70970                 acumY += areaElem;
70971                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70972                 if (!paths[areaIndex]) {
70973                     paths[areaIndex] = ['M', x, y];
70974                     componentPaths[areaIndex].push(['L', x, y]);
70975                 } else {
70976                     paths[areaIndex].push('L', x, y);
70977                     componentPaths[areaIndex].push(['L', x, y]);
70978                 }
70979                 if (!items[areaIndex]) {
70980                     items[areaIndex] = {
70981                         pointsUp: [],
70982                         pointsDown: [],
70983                         series: me
70984                     };
70985                 }
70986                 items[areaIndex].pointsUp.push([x, y]);
70987             }
70988         }
70989
70990         // Close the paths
70991         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70992             // Excluded series
70993             if (me.__excludes[areaIndex]) {
70994                 continue;
70995             }
70996             path = paths[areaIndex];
70997             // Close bottom path to the axis
70998             if (areaIndex == 0 || first) {
70999                 first = false;
71000                 path.push('L', x, bbox.y + bbox.height,
71001                           'L', bbox.x, bbox.y + bbox.height,
71002                           'Z');
71003             }
71004             // Close other paths to the one before them
71005             else {
71006                 componentPath = componentPaths[prevAreaIndex];
71007                 componentPath.reverse();
71008                 path.push('L', x, componentPath[0][2]);
71009                 for (i = 0; i < ln; i++) {
71010                     path.push(componentPath[i][0],
71011                               componentPath[i][1],
71012                               componentPath[i][2]);
71013                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
71014                 }
71015                 path.push('L', bbox.x, path[2], 'Z');
71016             }
71017             prevAreaIndex = areaIndex;
71018         }
71019         return {
71020             paths: paths,
71021             areasLen: bounds.areasLen
71022         };
71023     },
71024
71025     /**
71026      * Draws the series for the current chart.
71027      */
71028     drawSeries: function() {
71029         var me = this,
71030             chart = me.chart,
71031             store = chart.getChartStore(),
71032             surface = chart.surface,
71033             animate = chart.animate,
71034             group = me.group,
71035             endLineStyle = Ext.apply(me.seriesStyle, me.style),
71036             colorArrayStyle = me.colorArrayStyle,
71037             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
71038             areaIndex, areaElem, paths, path, rendererAttributes;
71039
71040         me.unHighlightItem();
71041         me.cleanHighlights();
71042
71043         if (!store || !store.getCount()) {
71044             return;
71045         }
71046
71047         paths = me.getPaths();
71048
71049         if (!me.areas) {
71050             me.areas = [];
71051         }
71052
71053         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
71054             // Excluded series
71055             if (me.__excludes[areaIndex]) {
71056                 continue;
71057             }
71058             if (!me.areas[areaIndex]) {
71059                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
71060                     type: 'path',
71061                     group: group,
71062                     // 'clip-rect': me.clipBox,
71063                     path: paths.paths[areaIndex],
71064                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
71065                     fill: colorArrayStyle[areaIndex % colorArrayLength]
71066                 }, endLineStyle || {}));
71067             }
71068             areaElem = me.areas[areaIndex];
71069             path = paths.paths[areaIndex];
71070             if (animate) {
71071                 //Add renderer to line. There is not a unique record associated with this.
71072                 rendererAttributes = me.renderer(areaElem, false, {
71073                     path: path,
71074                     // 'clip-rect': me.clipBox,
71075                     fill: colorArrayStyle[areaIndex % colorArrayLength],
71076                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
71077                 }, areaIndex, store);
71078                 //fill should not be used here but when drawing the special fill path object
71079                 me.animation = me.onAnimate(areaElem, {
71080                     to: rendererAttributes
71081                 });
71082             } else {
71083                 rendererAttributes = me.renderer(areaElem, false, {
71084                     path: path,
71085                     // 'clip-rect': me.clipBox,
71086                     hidden: false,
71087                     fill: colorArrayStyle[areaIndex % colorArrayLength],
71088                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
71089                 }, areaIndex, store);
71090                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
71091             }
71092         }
71093         me.renderLabels();
71094         me.renderCallouts();
71095     },
71096
71097     // @private
71098     onAnimate: function(sprite, attr) {
71099         sprite.show();
71100         return this.callParent(arguments);
71101     },
71102
71103     // @private
71104     onCreateLabel: function(storeItem, item, i, display) {
71105         var me = this,
71106             group = me.labelsGroup,
71107             config = me.label,
71108             bbox = me.bbox,
71109             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
71110
71111         return me.chart.surface.add(Ext.apply({
71112             'type': 'text',
71113             'text-anchor': 'middle',
71114             'group': group,
71115             'x': item.point[0],
71116             'y': bbox.y + bbox.height / 2
71117         }, endLabelStyle || {}));
71118     },
71119
71120     // @private
71121     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
71122         var me = this,
71123             chart = me.chart,
71124             resizing = chart.resizing,
71125             config = me.label,
71126             format = config.renderer,
71127             field = config.field,
71128             bbox = me.bbox,
71129             x = item.point[0],
71130             y = item.point[1],
71131             bb, width, height;
71132
71133         label.setAttributes({
71134             text: format(storeItem.get(field[index])),
71135             hidden: true
71136         }, true);
71137
71138         bb = label.getBBox();
71139         width = bb.width / 2;
71140         height = bb.height / 2;
71141
71142         x = x - width < bbox.x? bbox.x + width : x;
71143         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
71144         y = y - height < bbox.y? bbox.y + height : y;
71145         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
71146
71147         if (me.chart.animate && !me.chart.resizing) {
71148             label.show(true);
71149             me.onAnimate(label, {
71150                 to: {
71151                     x: x,
71152                     y: y
71153                 }
71154             });
71155         } else {
71156             label.setAttributes({
71157                 x: x,
71158                 y: y
71159             }, true);
71160             if (resizing) {
71161                 me.animation.on('afteranimate', function() {
71162                     label.show(true);
71163                 });
71164             } else {
71165                 label.show(true);
71166             }
71167         }
71168     },
71169
71170     // @private
71171     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
71172         var me = this,
71173             chart = me.chart,
71174             surface = chart.surface,
71175             resizing = chart.resizing,
71176             config = me.callouts,
71177             items = me.items,
71178             prev = (i == 0) ? false : items[i -1].point,
71179             next = (i == items.length -1) ? false : items[i +1].point,
71180             cur = item.point,
71181             dir, norm, normal, a, aprev, anext,
71182             bbox = callout.label.getBBox(),
71183             offsetFromViz = 30,
71184             offsetToSide = 10,
71185             offsetBox = 3,
71186             boxx, boxy, boxw, boxh,
71187             p, clipRect = me.clipRect,
71188             x, y;
71189
71190         //get the right two points
71191         if (!prev) {
71192             prev = cur;
71193         }
71194         if (!next) {
71195             next = cur;
71196         }
71197         a = (next[1] - prev[1]) / (next[0] - prev[0]);
71198         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
71199         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
71200
71201         norm = Math.sqrt(1 + a * a);
71202         dir = [1 / norm, a / norm];
71203         normal = [-dir[1], dir[0]];
71204
71205         //keep the label always on the outer part of the "elbow"
71206         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
71207             normal[0] *= -1;
71208             normal[1] *= -1;
71209         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
71210             normal[0] *= -1;
71211             normal[1] *= -1;
71212         }
71213
71214         //position
71215         x = cur[0] + normal[0] * offsetFromViz;
71216         y = cur[1] + normal[1] * offsetFromViz;
71217
71218         //box position and dimensions
71219         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
71220         boxy = y - bbox.height /2 - offsetBox;
71221         boxw = bbox.width + 2 * offsetBox;
71222         boxh = bbox.height + 2 * offsetBox;
71223
71224         //now check if we're out of bounds and invert the normal vector correspondingly
71225         //this may add new overlaps between labels (but labels won't be out of bounds).
71226         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
71227             normal[0] *= -1;
71228         }
71229         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
71230             normal[1] *= -1;
71231         }
71232
71233         //update positions
71234         x = cur[0] + normal[0] * offsetFromViz;
71235         y = cur[1] + normal[1] * offsetFromViz;
71236
71237         //update box position and dimensions
71238         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
71239         boxy = y - bbox.height /2 - offsetBox;
71240         boxw = bbox.width + 2 * offsetBox;
71241         boxh = bbox.height + 2 * offsetBox;
71242
71243         //set the line from the middle of the pie to the box.
71244         callout.lines.setAttributes({
71245             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
71246         }, true);
71247         //set box position
71248         callout.box.setAttributes({
71249             x: boxx,
71250             y: boxy,
71251             width: boxw,
71252             height: boxh
71253         }, true);
71254         //set text position
71255         callout.label.setAttributes({
71256             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
71257             y: y
71258         }, true);
71259         for (p in callout) {
71260             callout[p].show(true);
71261         }
71262     },
71263
71264     isItemInPoint: function(x, y, item, i) {
71265         var me = this,
71266             pointsUp = item.pointsUp,
71267             pointsDown = item.pointsDown,
71268             abs = Math.abs,
71269             dist = Infinity, p, pln, point;
71270
71271         for (p = 0, pln = pointsUp.length; p < pln; p++) {
71272             point = [pointsUp[p][0], pointsUp[p][1]];
71273             if (dist > abs(x - point[0])) {
71274                 dist = abs(x - point[0]);
71275             } else {
71276                 point = pointsUp[p -1];
71277                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
71278                     item.storeIndex = p -1;
71279                     item.storeField = me.yField[i];
71280                     item.storeItem = me.chart.store.getAt(p -1);
71281                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
71282                     return true;
71283                 } else {
71284                     break;
71285                 }
71286             }
71287         }
71288         return false;
71289     },
71290
71291     /**
71292      * Highlight this entire series.
71293      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
71294      */
71295     highlightSeries: function() {
71296         var area, to, fillColor;
71297         if (this._index !== undefined) {
71298             area = this.areas[this._index];
71299             if (area.__highlightAnim) {
71300                 area.__highlightAnim.paused = true;
71301             }
71302             area.__highlighted = true;
71303             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
71304             area.__prevFill = area.__prevFill || area.attr.fill;
71305             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
71306             fillColor = Ext.draw.Color.fromString(area.__prevFill);
71307             to = {
71308                 lineWidth: (area.__prevLineWidth || 0) + 2
71309             };
71310             if (fillColor) {
71311                 to.fill = fillColor.getLighter(0.2).toString();
71312             }
71313             else {
71314                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
71315             }
71316             if (this.chart.animate) {
71317                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
71318                     target: area,
71319                     to: to
71320                 }, this.chart.animate));
71321             }
71322             else {
71323                 area.setAttributes(to, true);
71324             }
71325         }
71326     },
71327
71328     /**
71329      * UnHighlight this entire series.
71330      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
71331      */
71332     unHighlightSeries: function() {
71333         var area;
71334         if (this._index !== undefined) {
71335             area = this.areas[this._index];
71336             if (area.__highlightAnim) {
71337                 area.__highlightAnim.paused = true;
71338             }
71339             if (area.__highlighted) {
71340                 area.__highlighted = false;
71341                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
71342                     target: area,
71343                     to: {
71344                         fill: area.__prevFill,
71345                         opacity: area.__prevOpacity,
71346                         lineWidth: area.__prevLineWidth
71347                     }
71348                 });
71349             }
71350         }
71351     },
71352
71353     /**
71354      * Highlight the specified item. If no item is provided the whole series will be highlighted.
71355      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
71356      */
71357     highlightItem: function(item) {
71358         var me = this,
71359             points, path;
71360         if (!item) {
71361             this.highlightSeries();
71362             return;
71363         }
71364         points = item._points;
71365         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
71366                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
71367         me.highlightSprite.setAttributes({
71368             path: path,
71369             hidden: false
71370         }, true);
71371     },
71372
71373     /**
71374      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
71375      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
71376      */
71377     unHighlightItem: function(item) {
71378         if (!item) {
71379             this.unHighlightSeries();
71380         }
71381
71382         if (this.highlightSprite) {
71383             this.highlightSprite.hide(true);
71384         }
71385     },
71386
71387     // @private
71388     hideAll: function() {
71389         if (!isNaN(this._index)) {
71390             this.__excludes[this._index] = true;
71391             this.areas[this._index].hide(true);
71392             this.drawSeries();
71393         }
71394     },
71395
71396     // @private
71397     showAll: function() {
71398         if (!isNaN(this._index)) {
71399             this.__excludes[this._index] = false;
71400             this.areas[this._index].show(true);
71401             this.drawSeries();
71402         }
71403     },
71404
71405     /**
71406      * Returns the color of the series (to be displayed as color for the series legend item).
71407      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
71408      */
71409     getLegendColor: function(index) {
71410         var me = this;
71411         return me.colorArrayStyle[index % me.colorArrayStyle.length];
71412     }
71413 });
71414
71415 /**
71416  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
71417  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
71418  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
71419  * A typical configuration object for the bar series could be:
71420  *
71421  *     @example
71422  *     var store = Ext.create('Ext.data.JsonStore', {
71423  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
71424  *         data: [
71425  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
71426  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
71427  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
71428  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
71429  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
71430  *         ]
71431  *     });
71432  *
71433  *     Ext.create('Ext.chart.Chart', {
71434  *         renderTo: Ext.getBody(),
71435  *         width: 500,
71436  *         height: 300,
71437  *         animate: true,
71438  *         store: store,
71439  *         axes: [{
71440  *             type: 'Numeric',
71441  *             position: 'bottom',
71442  *             fields: ['data1'],
71443  *             label: {
71444  *                 renderer: Ext.util.Format.numberRenderer('0,0')
71445  *             },
71446  *             title: 'Sample Values',
71447  *             grid: true,
71448  *             minimum: 0
71449  *         }, {
71450  *             type: 'Category',
71451  *             position: 'left',
71452  *             fields: ['name'],
71453  *             title: 'Sample Metrics'
71454  *         }],
71455  *         series: [{
71456  *             type: 'bar',
71457  *             axis: 'bottom',
71458  *             highlight: true,
71459  *             tips: {
71460  *               trackMouse: true,
71461  *               width: 140,
71462  *               height: 28,
71463  *               renderer: function(storeItem, item) {
71464  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
71465  *               }
71466  *             },
71467  *             label: {
71468  *               display: 'insideEnd',
71469  *                 field: 'data1',
71470  *                 renderer: Ext.util.Format.numberRenderer('0'),
71471  *                 orientation: 'horizontal',
71472  *                 color: '#333',
71473  *                 'text-anchor': 'middle'
71474  *             },
71475  *             xField: 'name',
71476  *             yField: ['data1']
71477  *         }]
71478  *     });
71479  *
71480  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
71481  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
71482  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
71483  * to display the information found in the `data1` property of each element store, to render a formated text with the
71484  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
71485  * other styles like `color`, `text-anchor`, etc.
71486  */
71487 Ext.define('Ext.chart.series.Bar', {
71488
71489     /* Begin Definitions */
71490
71491     extend: 'Ext.chart.series.Cartesian',
71492
71493     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
71494
71495     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
71496
71497     /* End Definitions */
71498
71499     type: 'bar',
71500
71501     alias: 'series.bar',
71502     /**
71503      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
71504      */
71505     column: false,
71506
71507     /**
71508      * @cfg style Style properties that will override the theming series styles.
71509      */
71510     style: {},
71511
71512     /**
71513      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
71514      */
71515     gutter: 38.2,
71516
71517     /**
71518      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
71519      */
71520     groupGutter: 38.2,
71521
71522     /**
71523      * @cfg {Number} xPadding Padding between the left/right axes and the bars
71524      */
71525     xPadding: 0,
71526
71527     /**
71528      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
71529      */
71530     yPadding: 10,
71531
71532     constructor: function(config) {
71533         this.callParent(arguments);
71534         var me = this,
71535             surface = me.chart.surface,
71536             shadow = me.chart.shadow,
71537             i, l;
71538         Ext.apply(me, config, {
71539             highlightCfg: {
71540                 lineWidth: 3,
71541                 stroke: '#55c',
71542                 opacity: 0.8,
71543                 color: '#f00'
71544             },
71545
71546             shadowAttributes: [{
71547                 "stroke-width": 6,
71548                 "stroke-opacity": 0.05,
71549                 stroke: 'rgb(200, 200, 200)',
71550                 translate: {
71551                     x: 1.2,
71552                     y: 1.2
71553                 }
71554             }, {
71555                 "stroke-width": 4,
71556                 "stroke-opacity": 0.1,
71557                 stroke: 'rgb(150, 150, 150)',
71558                 translate: {
71559                     x: 0.9,
71560                     y: 0.9
71561                 }
71562             }, {
71563                 "stroke-width": 2,
71564                 "stroke-opacity": 0.15,
71565                 stroke: 'rgb(100, 100, 100)',
71566                 translate: {
71567                     x: 0.6,
71568                     y: 0.6
71569                 }
71570             }]
71571         });
71572         me.group = surface.getGroup(me.seriesId + '-bars');
71573         if (shadow) {
71574             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
71575                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
71576             }
71577         }
71578     },
71579
71580     // @private sets the bar girth.
71581     getBarGirth: function() {
71582         var me = this,
71583             store = me.chart.getChartStore(),
71584             column = me.column,
71585             ln = store.getCount(),
71586             gutter = me.gutter / 100;
71587
71588         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
71589     },
71590
71591     // @private returns the gutters.
71592     getGutters: function() {
71593         var me = this,
71594             column = me.column,
71595             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
71596         return me.column ? [gutter, 0] : [0, gutter];
71597     },
71598
71599     // @private Get chart and data boundaries
71600     getBounds: function() {
71601         var me = this,
71602             chart = me.chart,
71603             store = chart.getChartStore(),
71604             bars = [].concat(me.yField),
71605             barsLen = bars.length,
71606             groupBarsLen = barsLen,
71607             groupGutter = me.groupGutter / 100,
71608             column = me.column,
71609             xPadding = me.xPadding,
71610             yPadding = me.yPadding,
71611             stacked = me.stacked,
71612             barWidth = me.getBarGirth(),
71613             math = Math,
71614             mmax = math.max,
71615             mabs = math.abs,
71616             groupBarWidth, bbox, minY, maxY, axis, out,
71617             scale, zero, total, rec, j, plus, minus;
71618
71619         me.setBBox(true);
71620         bbox = me.bbox;
71621
71622         //Skip excluded series
71623         if (me.__excludes) {
71624             for (j = 0, total = me.__excludes.length; j < total; j++) {
71625                 if (me.__excludes[j]) {
71626                     groupBarsLen--;
71627                 }
71628             }
71629         }
71630
71631         if (me.axis) {
71632             axis = chart.axes.get(me.axis);
71633             if (axis) {
71634                 out = axis.calcEnds();
71635                 minY = out.from;
71636                 maxY = out.to;
71637             }
71638         }
71639
71640         if (me.yField && !Ext.isNumber(minY)) {
71641             axis = Ext.create('Ext.chart.axis.Axis', {
71642                 chart: chart,
71643                 fields: [].concat(me.yField)
71644             });
71645             out = axis.calcEnds();
71646             minY = out.from;
71647             maxY = out.to;
71648         }
71649
71650         if (!Ext.isNumber(minY)) {
71651             minY = 0;
71652         }
71653         if (!Ext.isNumber(maxY)) {
71654             maxY = 0;
71655         }
71656         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
71657         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
71658         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
71659
71660         if (stacked) {
71661             total = [[], []];
71662             store.each(function(record, i) {
71663                 total[0][i] = total[0][i] || 0;
71664                 total[1][i] = total[1][i] || 0;
71665                 for (j = 0; j < barsLen; j++) {
71666                     if (me.__excludes && me.__excludes[j]) {
71667                         continue;
71668                     }
71669                     rec = record.get(bars[j]);
71670                     total[+(rec > 0)][i] += mabs(rec);
71671                 }
71672             });
71673             total[+(maxY > 0)].push(mabs(maxY));
71674             total[+(minY > 0)].push(mabs(minY));
71675             minus = mmax.apply(math, total[0]);
71676             plus = mmax.apply(math, total[1]);
71677             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
71678             zero = zero + minus * scale * (column ? -1 : 1);
71679         }
71680         else if (minY / maxY < 0) {
71681             zero = zero - minY * scale * (column ? -1 : 1);
71682         }
71683         return {
71684             bars: bars,
71685             bbox: bbox,
71686             barsLen: barsLen,
71687             groupBarsLen: groupBarsLen,
71688             barWidth: barWidth,
71689             groupBarWidth: groupBarWidth,
71690             scale: scale,
71691             zero: zero,
71692             xPadding: xPadding,
71693             yPadding: yPadding,
71694             signed: minY / maxY < 0,
71695             minY: minY,
71696             maxY: maxY
71697         };
71698     },
71699
71700     // @private Build an array of paths for the chart
71701     getPaths: function() {
71702         var me = this,
71703             chart = me.chart,
71704             store = chart.getChartStore(),
71705             bounds = me.bounds = me.getBounds(),
71706             items = me.items = [],
71707             gutter = me.gutter / 100,
71708             groupGutter = me.groupGutter / 100,
71709             animate = chart.animate,
71710             column = me.column,
71711             group = me.group,
71712             enableShadows = chart.shadow,
71713             shadowGroups = me.shadowGroups,
71714             shadowAttributes = me.shadowAttributes,
71715             shadowGroupsLn = shadowGroups.length,
71716             bbox = bounds.bbox,
71717             xPadding = me.xPadding,
71718             yPadding = me.yPadding,
71719             stacked = me.stacked,
71720             barsLen = bounds.barsLen,
71721             colors = me.colorArrayStyle,
71722             colorLength = colors && colors.length || 0,
71723             math = Math,
71724             mmax = math.max,
71725             mmin = math.min,
71726             mabs = math.abs,
71727             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
71728             shadowIndex, shadow, sprite, offset, floorY;
71729
71730         store.each(function(record, i, total) {
71731             bottom = bounds.zero;
71732             top = bounds.zero;
71733             totalDim = 0;
71734             totalNegDim = 0;
71735             hasShadow = false;
71736             for (j = 0, counter = 0; j < barsLen; j++) {
71737                 // Excluded series
71738                 if (me.__excludes && me.__excludes[j]) {
71739                     continue;
71740                 }
71741                 yValue = record.get(bounds.bars[j]);
71742                 height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
71743                 barAttr = {
71744                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
71745                 };
71746                 if (column) {
71747                     Ext.apply(barAttr, {
71748                         height: height,
71749                         width: mmax(bounds.groupBarWidth, 0),
71750                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
71751                         y: bottom - height
71752                     });
71753                 }
71754                 else {
71755                     // draw in reverse order
71756                     offset = (total - 1) - i;
71757                     Ext.apply(barAttr, {
71758                         height: mmax(bounds.groupBarWidth, 0),
71759                         width: height + (bottom == bounds.zero),
71760                         x: bottom + (bottom != bounds.zero),
71761                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
71762                     });
71763                 }
71764                 if (height < 0) {
71765                     if (column) {
71766                         barAttr.y = top;
71767                         barAttr.height = mabs(height);
71768                     } else {
71769                         barAttr.x = top + height;
71770                         barAttr.width = mabs(height);
71771                     }
71772                 }
71773                 if (stacked) {
71774                     if (height < 0) {
71775                         top += height * (column ? -1 : 1);
71776                     } else {
71777                         bottom += height * (column ? -1 : 1);
71778                     }
71779                     totalDim += mabs(height);
71780                     if (height < 0) {
71781                         totalNegDim += mabs(height);
71782                     }
71783                 }
71784                 barAttr.x = Math.floor(barAttr.x) + 1;
71785                 floorY = Math.floor(barAttr.y);
71786                 if (!Ext.isIE9 && barAttr.y > floorY) {
71787                     floorY--;
71788                 }
71789                 barAttr.y = floorY;
71790                 barAttr.width = Math.floor(barAttr.width);
71791                 barAttr.height = Math.floor(barAttr.height);
71792                 items.push({
71793                     series: me,
71794                     storeItem: record,
71795                     value: [record.get(me.xField), yValue],
71796                     attr: barAttr,
71797                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
71798                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
71799                 });
71800                 // When resizing, reset before animating
71801                 if (animate && chart.resizing) {
71802                     attrs = column ? {
71803                         x: barAttr.x,
71804                         y: bounds.zero,
71805                         width: barAttr.width,
71806                         height: 0
71807                     } : {
71808                         x: bounds.zero,
71809                         y: barAttr.y,
71810                         width: 0,
71811                         height: barAttr.height
71812                     };
71813                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
71814                         hasShadow = true;
71815                         //update shadows
71816                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71817                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
71818                             if (shadow) {
71819                                 shadow.setAttributes(attrs, true);
71820                             }
71821                         }
71822                     }
71823                     //update sprite position and width/height
71824                     sprite = group.getAt(i * barsLen + j);
71825                     if (sprite) {
71826                         sprite.setAttributes(attrs, true);
71827                     }
71828                 }
71829                 counter++;
71830             }
71831             if (stacked && items.length) {
71832                 items[i * counter].totalDim = totalDim;
71833                 items[i * counter].totalNegDim = totalNegDim;
71834             }
71835         }, me);
71836     },
71837
71838     // @private render/setAttributes on the shadows
71839     renderShadows: function(i, barAttr, baseAttrs, bounds) {
71840         var me = this,
71841             chart = me.chart,
71842             surface = chart.surface,
71843             animate = chart.animate,
71844             stacked = me.stacked,
71845             shadowGroups = me.shadowGroups,
71846             shadowAttributes = me.shadowAttributes,
71847             shadowGroupsLn = shadowGroups.length,
71848             store = chart.getChartStore(),
71849             column = me.column,
71850             items = me.items,
71851             shadows = [],
71852             zero = bounds.zero,
71853             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
71854
71855         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
71856             j = i / bounds.groupBarsLen;
71857             //create shadows
71858             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71859                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
71860                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
71861                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
71862                 if (!shadow) {
71863                     shadow = surface.add(Ext.apply({
71864                         type: 'rect',
71865                         group: shadowGroups[shadowIndex]
71866                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
71867                 }
71868                 if (stacked) {
71869                     totalDim = items[i].totalDim;
71870                     totalNegDim = items[i].totalNegDim;
71871                     if (column) {
71872                         shadowBarAttr.y = zero - totalNegDim;
71873                         shadowBarAttr.height = totalDim;
71874                     }
71875                     else {
71876                         shadowBarAttr.x = zero - totalNegDim;
71877                         shadowBarAttr.width = totalDim;
71878                     }
71879                 }
71880                 if (animate) {
71881                     if (!stacked) {
71882                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
71883                         me.onAnimate(shadow, { to: rendererAttributes });
71884                     }
71885                     else {
71886                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
71887                         shadow.setAttributes(rendererAttributes, true);
71888                     }
71889                 }
71890                 else {
71891                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
71892                     shadow.setAttributes(rendererAttributes, true);
71893                 }
71894                 shadows.push(shadow);
71895             }
71896         }
71897         return shadows;
71898     },
71899
71900     /**
71901      * Draws the series for the current chart.
71902      */
71903     drawSeries: function() {
71904         var me = this,
71905             chart = me.chart,
71906             store = chart.getChartStore(),
71907             surface = chart.surface,
71908             animate = chart.animate,
71909             stacked = me.stacked,
71910             column = me.column,
71911             enableShadows = chart.shadow,
71912             shadowGroups = me.shadowGroups,
71913             shadowGroupsLn = shadowGroups.length,
71914             group = me.group,
71915             seriesStyle = me.seriesStyle,
71916             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
71917             bounds, endSeriesStyle, barAttr, attrs, anim;
71918
71919         if (!store || !store.getCount()) {
71920             return;
71921         }
71922
71923         //fill colors are taken from the colors array.
71924         delete seriesStyle.fill;
71925         endSeriesStyle = Ext.apply(seriesStyle, this.style);
71926         me.unHighlightItem();
71927         me.cleanHighlights();
71928
71929         me.getPaths();
71930         bounds = me.bounds;
71931         items = me.items;
71932
71933         baseAttrs = column ? {
71934             y: bounds.zero,
71935             height: 0
71936         } : {
71937             x: bounds.zero,
71938             width: 0
71939         };
71940         ln = items.length;
71941         // Create new or reuse sprites and animate/display
71942         for (i = 0; i < ln; i++) {
71943             sprite = group.getAt(i);
71944             barAttr = items[i].attr;
71945
71946             if (enableShadows) {
71947                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
71948             }
71949
71950             // Create a new sprite if needed (no height)
71951             if (!sprite) {
71952                 attrs = Ext.apply({}, baseAttrs, barAttr);
71953                 attrs = Ext.apply(attrs, endSeriesStyle || {});
71954                 sprite = surface.add(Ext.apply({}, {
71955                     type: 'rect',
71956                     group: group
71957                 }, attrs));
71958             }
71959             if (animate) {
71960                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
71961                 sprite._to = rendererAttributes;
71962                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
71963                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
71964                     j = i / bounds.barsLen;
71965                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71966                         anim.on('afteranimate', function() {
71967                             this.show(true);
71968                         }, shadowGroups[shadowIndex].getAt(j));
71969                     }
71970                 }
71971             }
71972             else {
71973                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
71974                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
71975             }
71976             items[i].sprite = sprite;
71977         }
71978
71979         // Hide unused sprites
71980         ln = group.getCount();
71981         for (j = i; j < ln; j++) {
71982             group.getAt(j).hide(true);
71983         }
71984         // Hide unused shadows
71985         if (enableShadows) {
71986             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71987                 shadowGroup = shadowGroups[shadowIndex];
71988                 ln = shadowGroup.getCount();
71989                 for (j = i; j < ln; j++) {
71990                     shadowGroup.getAt(j).hide(true);
71991                 }
71992             }
71993         }
71994         me.renderLabels();
71995     },
71996
71997     // @private handled when creating a label.
71998     onCreateLabel: function(storeItem, item, i, display) {
71999         var me = this,
72000             surface = me.chart.surface,
72001             group = me.labelsGroup,
72002             config = me.label,
72003             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
72004             sprite;
72005         return surface.add(Ext.apply({
72006             type: 'text',
72007             group: group
72008         }, endLabelStyle || {}));
72009     },
72010
72011     // @private callback used when placing a label.
72012     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
72013         // Determine the label's final position. Starts with the configured preferred value but
72014         // may get flipped from inside to outside or vice-versa depending on space.
72015         var me = this,
72016             opt = me.bounds,
72017             groupBarWidth = opt.groupBarWidth,
72018             column = me.column,
72019             chart = me.chart,
72020             chartBBox = chart.chartBBox,
72021             resizing = chart.resizing,
72022             xValue = item.value[0],
72023             yValue = item.value[1],
72024             attr = item.attr,
72025             config = me.label,
72026             rotate = config.orientation == 'vertical',
72027             field = [].concat(config.field),
72028             format = config.renderer,
72029             text = format(storeItem.get(field[index])),
72030             size = me.getLabelSize(text),
72031             width = size.width,
72032             height = size.height,
72033             zero = opt.zero,
72034             outside = 'outside',
72035             insideStart = 'insideStart',
72036             insideEnd = 'insideEnd',
72037             offsetX = 10,
72038             offsetY = 6,
72039             signed = opt.signed,
72040             x, y, finalAttr;
72041
72042         label.setAttributes({
72043             text: text
72044         });
72045
72046         label.isOutside = false;
72047         if (column) {
72048             if (display == outside) {
72049                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
72050                     display = insideEnd;
72051                 }
72052             } else {
72053                 if (height + offsetY > attr.height) {
72054                     display = outside;
72055                     label.isOutside = true;
72056                 }
72057             }
72058             x = attr.x + groupBarWidth / 2;
72059             y = display == insideStart ?
72060                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
72061                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
72062                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
72063         }
72064         else {
72065             if (display == outside) {
72066                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
72067                     display = insideEnd;
72068                 }
72069             }
72070             else {
72071                 if (width + offsetX > attr.width) {
72072                     display = outside;
72073                     label.isOutside = true;
72074                 }
72075             }
72076             x = display == insideStart ?
72077                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
72078                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
72079                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
72080             y = attr.y + groupBarWidth / 2;
72081         }
72082         //set position
72083         finalAttr = {
72084             x: x,
72085             y: y
72086         };
72087         //rotate
72088         if (rotate) {
72089             finalAttr.rotate = {
72090                 x: x,
72091                 y: y,
72092                 degrees: 270
72093             };
72094         }
72095         //check for resizing
72096         if (animate && resizing) {
72097             if (column) {
72098                 x = attr.x + attr.width / 2;
72099                 y = zero;
72100             } else {
72101                 x = zero;
72102                 y = attr.y + attr.height / 2;
72103             }
72104             label.setAttributes({
72105                 x: x,
72106                 y: y
72107             }, true);
72108             if (rotate) {
72109                 label.setAttributes({
72110                     rotate: {
72111                         x: x,
72112                         y: y,
72113                         degrees: 270
72114                     }
72115                 }, true);
72116             }
72117         }
72118         //handle animation
72119         if (animate) {
72120             me.onAnimate(label, { to: finalAttr });
72121         }
72122         else {
72123             label.setAttributes(Ext.apply(finalAttr, {
72124                 hidden: false
72125             }), true);
72126         }
72127     },
72128
72129     /* @private
72130      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
72131      * changing visible sprites.
72132      * @param value
72133      */
72134     getLabelSize: function(value) {
72135         var tester = this.testerLabel,
72136             config = this.label,
72137             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
72138             rotated = config.orientation === 'vertical',
72139             bbox, w, h,
72140             undef;
72141         if (!tester) {
72142             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
72143                 type: 'text',
72144                 opacity: 0
72145             }, endLabelStyle));
72146         }
72147         tester.setAttributes({
72148             text: value
72149         }, true);
72150
72151         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
72152         bbox = tester.getBBox();
72153         w = bbox.width;
72154         h = bbox.height;
72155         return {
72156             width: rotated ? h : w,
72157             height: rotated ? w : h
72158         };
72159     },
72160
72161     // @private used to animate label, markers and other sprites.
72162     onAnimate: function(sprite, attr) {
72163         sprite.show();
72164         return this.callParent(arguments);
72165     },
72166
72167     isItemInPoint: function(x, y, item) {
72168         var bbox = item.sprite.getBBox();
72169         return bbox.x <= x && bbox.y <= y
72170             && (bbox.x + bbox.width) >= x
72171             && (bbox.y + bbox.height) >= y;
72172     },
72173
72174     // @private hide all markers
72175     hideAll: function() {
72176         var axes = this.chart.axes;
72177         if (!isNaN(this._index)) {
72178             if (!this.__excludes) {
72179                 this.__excludes = [];
72180             }
72181             this.__excludes[this._index] = true;
72182             this.drawSeries();
72183             axes.each(function(axis) {
72184                 axis.drawAxis();
72185             });
72186         }
72187     },
72188
72189     // @private show all markers
72190     showAll: function() {
72191         var axes = this.chart.axes;
72192         if (!isNaN(this._index)) {
72193             if (!this.__excludes) {
72194                 this.__excludes = [];
72195             }
72196             this.__excludes[this._index] = false;
72197             this.drawSeries();
72198             axes.each(function(axis) {
72199                 axis.drawAxis();
72200             });
72201         }
72202     },
72203
72204     /**
72205      * Returns a string with the color to be used for the series legend item.
72206      * @param index
72207      */
72208     getLegendColor: function(index) {
72209         var me = this,
72210             colorLength = me.colorArrayStyle.length;
72211
72212         if (me.style && me.style.fill) {
72213             return me.style.fill;
72214         } else {
72215             return me.colorArrayStyle[index % colorLength];
72216         }
72217     },
72218
72219     highlightItem: function(item) {
72220         this.callParent(arguments);
72221         this.renderLabels();
72222     },
72223
72224     unHighlightItem: function() {
72225         this.callParent(arguments);
72226         this.renderLabels();
72227     },
72228
72229     cleanHighlights: function() {
72230         this.callParent(arguments);
72231         this.renderLabels();
72232     }
72233 });
72234 /**
72235  * @class Ext.chart.series.Column
72236  * @extends Ext.chart.series.Bar
72237  *
72238  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
72239  * visualization technique to display quantitative information for different categories that can
72240  * show some progression (or regression) in the data set. As with all other series, the Column Series
72241  * must be appended in the *series* Chart array configuration. See the Chart documentation for more
72242  * information. A typical configuration object for the column series could be:
72243  *
72244  *     @example
72245  *     var store = Ext.create('Ext.data.JsonStore', {
72246  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72247  *         data: [
72248  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72249  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72250  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72251  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72252  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
72253  *         ]
72254  *     });
72255  *
72256  *     Ext.create('Ext.chart.Chart', {
72257  *         renderTo: Ext.getBody(),
72258  *         width: 500,
72259  *         height: 300,
72260  *         animate: true,
72261  *         store: store,
72262  *         axes: [
72263  *             {
72264  *                 type: 'Numeric',
72265  *                 position: 'left',
72266  *                 fields: ['data1'],
72267  *                 label: {
72268  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72269  *                 },
72270  *                 title: 'Sample Values',
72271  *                 grid: true,
72272  *                 minimum: 0
72273  *             },
72274  *             {
72275  *                 type: 'Category',
72276  *                 position: 'bottom',
72277  *                 fields: ['name'],
72278  *                 title: 'Sample Metrics'
72279  *             }
72280  *         ],
72281  *         series: [
72282  *             {
72283  *                 type: 'column',
72284  *                 axis: 'left',
72285  *                 highlight: true,
72286  *                 tips: {
72287  *                   trackMouse: true,
72288  *                   width: 140,
72289  *                   height: 28,
72290  *                   renderer: function(storeItem, item) {
72291  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
72292  *                   }
72293  *                 },
72294  *                 label: {
72295  *                   display: 'insideEnd',
72296  *                   'text-anchor': 'middle',
72297  *                     field: 'data1',
72298  *                     renderer: Ext.util.Format.numberRenderer('0'),
72299  *                     orientation: 'vertical',
72300  *                     color: '#333'
72301  *                 },
72302  *                 xField: 'name',
72303  *                 yField: 'data1'
72304  *             }
72305  *         ]
72306  *     });
72307  *
72308  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
72309  * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
72310  * field to the data store `name` property and the `yField` as the data1 property of a store element.
72311  */
72312 Ext.define('Ext.chart.series.Column', {
72313
72314     /* Begin Definitions */
72315
72316     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
72317
72318     extend: 'Ext.chart.series.Bar',
72319
72320     /* End Definitions */
72321
72322     type: 'column',
72323     alias: 'series.column',
72324
72325     column: true,
72326
72327     /**
72328      * @cfg {Number} xPadding
72329      * Padding between the left/right axes and the bars
72330      */
72331     xPadding: 10,
72332
72333     /**
72334      * @cfg {Number} yPadding
72335      * Padding between the top/bottom axes and the bars
72336      */
72337     yPadding: 0
72338 });
72339 /**
72340  * @class Ext.chart.series.Gauge
72341  * @extends Ext.chart.series.Series
72342  * 
72343  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
72344  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
72345  * visualization and using the `setValue` method to adjust the value you want.
72346  *
72347  * A chart/series configuration for the Gauge visualization could look like this:
72348  * 
72349  *     {
72350  *         xtype: 'chart',
72351  *         store: store,
72352  *         axes: [{
72353  *             type: 'gauge',
72354  *             position: 'gauge',
72355  *             minimum: 0,
72356  *             maximum: 100,
72357  *             steps: 10,
72358  *             margin: -10
72359  *         }],
72360  *         series: [{
72361  *             type: 'gauge',
72362  *             field: 'data1',
72363  *             donut: false,
72364  *             colorSet: ['#F49D10', '#ddd']
72365  *         }]
72366  *     }
72367  * 
72368  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
72369  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
72370  * the visual display and the color set to be used with the visualization.
72371  * 
72372  * @xtype gauge
72373  */
72374 Ext.define('Ext.chart.series.Gauge', {
72375
72376     /* Begin Definitions */
72377
72378     extend: 'Ext.chart.series.Series',
72379
72380     /* End Definitions */
72381
72382     type: "gauge",
72383     alias: 'series.gauge',
72384
72385     rad: Math.PI / 180,
72386
72387     /**
72388      * @cfg {Number} highlightDuration
72389      * The duration for the pie slice highlight effect.
72390      */
72391     highlightDuration: 150,
72392
72393     /**
72394      * @cfg {String} angleField (required)
72395      * The store record field name to be used for the pie angles.
72396      * The values bound to this field name must be positive real numbers.
72397      */
72398     angleField: false,
72399
72400     /**
72401      * @cfg {Boolean} needle
72402      * Use the Gauge Series as an area series or add a needle to it. Default's false.
72403      */
72404     needle: false,
72405     
72406     /**
72407      * @cfg {Boolean/Number} donut
72408      * Use the entire disk or just a fraction of it for the gauge. Default's false.
72409      */
72410     donut: false,
72411
72412     /**
72413      * @cfg {Boolean} showInLegend
72414      * Whether to add the pie chart elements as legend items. Default's false.
72415      */
72416     showInLegend: false,
72417
72418     /**
72419      * @cfg {Object} style
72420      * An object containing styles for overriding series styles from Theming.
72421      */
72422     style: {},
72423     
72424     constructor: function(config) {
72425         this.callParent(arguments);
72426         var me = this,
72427             chart = me.chart,
72428             surface = chart.surface,
72429             store = chart.store,
72430             shadow = chart.shadow, i, l, cfg;
72431         Ext.apply(me, config, {
72432             shadowAttributes: [{
72433                 "stroke-width": 6,
72434                 "stroke-opacity": 1,
72435                 stroke: 'rgb(200, 200, 200)',
72436                 translate: {
72437                     x: 1.2,
72438                     y: 2
72439                 }
72440             },
72441             {
72442                 "stroke-width": 4,
72443                 "stroke-opacity": 1,
72444                 stroke: 'rgb(150, 150, 150)',
72445                 translate: {
72446                     x: 0.9,
72447                     y: 1.5
72448                 }
72449             },
72450             {
72451                 "stroke-width": 2,
72452                 "stroke-opacity": 1,
72453                 stroke: 'rgb(100, 100, 100)',
72454                 translate: {
72455                     x: 0.6,
72456                     y: 1
72457                 }
72458             }]
72459         });
72460         me.group = surface.getGroup(me.seriesId);
72461         if (shadow) {
72462             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
72463                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
72464             }
72465         }
72466         surface.customAttributes.segment = function(opt) {
72467             return me.getSegment(opt);
72468         };
72469     },
72470     
72471     //@private updates some onbefore render parameters.
72472     initialize: function() {
72473         var me = this,
72474             store = me.chart.getChartStore();
72475         //Add yFields to be used in Legend.js
72476         me.yField = [];
72477         if (me.label.field) {
72478             store.each(function(rec) {
72479                 me.yField.push(rec.get(me.label.field));
72480             });
72481         }
72482     },
72483
72484     // @private returns an object with properties for a Slice
72485     getSegment: function(opt) {
72486         var me = this,
72487             rad = me.rad,
72488             cos = Math.cos,
72489             sin = Math.sin,
72490             abs = Math.abs,
72491             x = me.centerX,
72492             y = me.centerY,
72493             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
72494             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
72495             delta = 1e-2,
72496             r = opt.endRho - opt.startRho,
72497             startAngle = opt.startAngle,
72498             endAngle = opt.endAngle,
72499             midAngle = (startAngle + endAngle) / 2 * rad,
72500             margin = opt.margin || 0,
72501             flag = abs(endAngle - startAngle) > 180,
72502             a1 = Math.min(startAngle, endAngle) * rad,
72503             a2 = Math.max(startAngle, endAngle) * rad,
72504             singleSlice = false;
72505
72506         x += margin * cos(midAngle);
72507         y += margin * sin(midAngle);
72508
72509         x1 = x + opt.startRho * cos(a1);
72510         y1 = y + opt.startRho * sin(a1);
72511
72512         x2 = x + opt.endRho * cos(a1);
72513         y2 = y + opt.endRho * sin(a1);
72514
72515         x3 = x + opt.startRho * cos(a2);
72516         y3 = y + opt.startRho * sin(a2);
72517
72518         x4 = x + opt.endRho * cos(a2);
72519         y4 = y + opt.endRho * sin(a2);
72520
72521         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
72522             singleSlice = true;
72523         }
72524         //Solves mysterious clipping bug with IE
72525         if (singleSlice) {
72526             return {
72527                 path: [
72528                 ["M", x1, y1],
72529                 ["L", x2, y2],
72530                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
72531                 ["Z"]]
72532             };
72533         } else {
72534             return {
72535                 path: [
72536                 ["M", x1, y1],
72537                 ["L", x2, y2],
72538                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
72539                 ["L", x3, y3],
72540                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
72541                 ["Z"]]
72542             };
72543         }
72544     },
72545
72546     // @private utility function to calculate the middle point of a pie slice.
72547     calcMiddle: function(item) {
72548         var me = this,
72549             rad = me.rad,
72550             slice = item.slice,
72551             x = me.centerX,
72552             y = me.centerY,
72553             startAngle = slice.startAngle,
72554             endAngle = slice.endAngle,
72555             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
72556             donut = +me.donut,
72557             a1 = Math.min(startAngle, endAngle) * rad,
72558             a2 = Math.max(startAngle, endAngle) * rad,
72559             midAngle = -(a1 + (a2 - a1) / 2),
72560             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
72561             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
72562
72563         item.middle = {
72564             x: xm,
72565             y: ym
72566         };
72567     },
72568
72569     /**
72570      * Draws the series for the current chart.
72571      */
72572     drawSeries: function() {
72573         var me = this,
72574             chart = me.chart,
72575             store = chart.getChartStore(),
72576             group = me.group,
72577             animate = me.chart.animate,
72578             axis = me.chart.axes.get(0),
72579             minimum = axis && axis.minimum || me.minimum || 0,
72580             maximum = axis && axis.maximum || me.maximum || 0,
72581             field = me.angleField || me.field || me.xField,
72582             surface = chart.surface,
72583             chartBBox = chart.chartBBox,
72584             rad = me.rad,
72585             donut = +me.donut,
72586             values = {},
72587             items = [],
72588             seriesStyle = me.seriesStyle,
72589             seriesLabelStyle = me.seriesLabelStyle,
72590             colorArrayStyle = me.colorArrayStyle,
72591             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
72592             gutterX = chart.maxGutter[0],
72593             gutterY = chart.maxGutter[1],
72594             cos = Math.cos,
72595             sin = Math.sin,
72596             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
72597             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
72598             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
72599         
72600         Ext.apply(seriesStyle, me.style || {});
72601
72602         me.setBBox();
72603         bbox = me.bbox;
72604
72605         //override theme colors
72606         if (me.colorSet) {
72607             colorArrayStyle = me.colorSet;
72608             colorArrayLength = colorArrayStyle.length;
72609         }
72610         
72611         //if not store or store is empty then there's nothing to draw
72612         if (!store || !store.getCount()) {
72613             return;
72614         }
72615         
72616         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
72617         centerY = me.centerY = chartBBox.y + chartBBox.height;
72618         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
72619         me.slices = slices = [];
72620         me.items = items = [];
72621         
72622         if (!me.value) {
72623             record = store.getAt(0);
72624             me.value = record.get(field);
72625         }
72626         
72627         value = me.value;
72628         if (me.needle) {
72629             sliceA = {
72630                 series: me,
72631                 value: value,
72632                 startAngle: -180,
72633                 endAngle: 0,
72634                 rho: me.radius
72635             };
72636             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
72637             slices.push(sliceA);
72638         } else {
72639             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
72640             sliceA = {
72641                 series: me,
72642                 value: value,
72643                 startAngle: -180,
72644                 endAngle: splitAngle,
72645                 rho: me.radius
72646             };
72647             sliceB = {
72648                 series: me,
72649                 value: me.maximum - value,
72650                 startAngle: splitAngle,
72651                 endAngle: 0,
72652                 rho: me.radius
72653             };
72654             slices.push(sliceA, sliceB);
72655         }
72656         
72657         //do pie slices after.
72658         for (i = 0, ln = slices.length; i < ln; i++) {
72659             slice = slices[i];
72660             sprite = group.getAt(i);
72661             //set pie slice properties
72662             rendererAttributes = Ext.apply({
72663                 segment: {
72664                     startAngle: slice.startAngle,
72665                     endAngle: slice.endAngle,
72666                     margin: 0,
72667                     rho: slice.rho,
72668                     startRho: slice.rho * +donut / 100,
72669                     endRho: slice.rho
72670                 } 
72671             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72672
72673             item = Ext.apply({},
72674             rendererAttributes.segment, {
72675                 slice: slice,
72676                 series: me,
72677                 storeItem: record,
72678                 index: i
72679             });
72680             items[i] = item;
72681             // Create a new sprite if needed (no height)
72682             if (!sprite) {
72683                 spriteOptions = Ext.apply({
72684                     type: "path",
72685                     group: group
72686                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72687                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
72688             }
72689             slice.sprite = slice.sprite || [];
72690             item.sprite = sprite;
72691             slice.sprite.push(sprite);
72692             if (animate) {
72693                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
72694                 sprite._to = rendererAttributes;
72695                 me.onAnimate(sprite, {
72696                     to: rendererAttributes
72697                 });
72698             } else {
72699                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
72700                     hidden: false
72701                 }), i, store);
72702                 sprite.setAttributes(rendererAttributes, true);
72703             }
72704         }
72705         
72706         if (me.needle) {
72707             splitAngle = splitAngle * Math.PI / 180;
72708             
72709             if (!me.needleSprite) {
72710                 me.needleSprite = me.chart.surface.add({
72711                     type: 'path',
72712                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72713                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72714                            'L', centerX + me.radius * cos(splitAngle),
72715                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
72716                     'stroke-width': 4,
72717                     'stroke': '#222'
72718                 });
72719             } else {
72720                 if (animate) {
72721                     me.onAnimate(me.needleSprite, {
72722                         to: {
72723                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72724                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72725                                'L', centerX + me.radius * cos(splitAngle),
72726                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72727                         }
72728                     });
72729                 } else {
72730                     me.needleSprite.setAttributes({
72731                         type: 'path',
72732                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72733                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72734                                'L', centerX + me.radius * cos(splitAngle),
72735                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72736                     });
72737                 }
72738             }
72739             me.needleSprite.setAttributes({
72740                 hidden: false    
72741             }, true);
72742         }
72743         
72744         delete me.value;
72745     },
72746     
72747     /**
72748      * Sets the Gauge chart to the current specified value.
72749     */
72750     setValue: function (value) {
72751         this.value = value;
72752         this.drawSeries();
72753     },
72754
72755     // @private callback for when creating a label sprite.
72756     onCreateLabel: function(storeItem, item, i, display) {},
72757
72758     // @private callback for when placing a label sprite.
72759     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
72760
72761     // @private callback for when placing a callout.
72762     onPlaceCallout: function() {},
72763
72764     // @private handles sprite animation for the series.
72765     onAnimate: function(sprite, attr) {
72766         sprite.show();
72767         return this.callParent(arguments);
72768     },
72769
72770     isItemInPoint: function(x, y, item, i) {
72771         return false;
72772     },
72773     
72774     // @private shows all elements in the series.
72775     showAll: function() {
72776         if (!isNaN(this._index)) {
72777             this.__excludes[this._index] = false;
72778             this.drawSeries();
72779         }
72780     },
72781     
72782     /**
72783      * Returns the color of the series (to be displayed as color for the series legend item).
72784      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
72785      */
72786     getLegendColor: function(index) {
72787         var me = this;
72788         return me.colorArrayStyle[index % me.colorArrayStyle.length];
72789     }
72790 });
72791
72792
72793 /**
72794  * @class Ext.chart.series.Line
72795  * @extends Ext.chart.series.Cartesian
72796  *
72797  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
72798  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
72799  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
72800  * documentation for more information. A typical configuration object for the line series could be:
72801  *
72802  *     @example
72803  *     var store = Ext.create('Ext.data.JsonStore', {
72804  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72805  *         data: [
72806  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72807  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72808  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72809  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72810  *             { 'name': 'metric five',  'data1': 4,  'data2': 4,  'data3': 36, 'data4': 13, 'data5': 33 }
72811  *         ]
72812  *     });
72813  *
72814  *     Ext.create('Ext.chart.Chart', {
72815  *         renderTo: Ext.getBody(),
72816  *         width: 500,
72817  *         height: 300,
72818  *         animate: true,
72819  *         store: store,
72820  *         axes: [
72821  *             {
72822  *                 type: 'Numeric',
72823  *                 position: 'left',
72824  *                 fields: ['data1', 'data2'],
72825  *                 label: {
72826  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72827  *                 },
72828  *                 title: 'Sample Values',
72829  *                 grid: true,
72830  *                 minimum: 0
72831  *             },
72832  *             {
72833  *                 type: 'Category',
72834  *                 position: 'bottom',
72835  *                 fields: ['name'],
72836  *                 title: 'Sample Metrics'
72837  *             }
72838  *         ],
72839  *         series: [
72840  *             {
72841  *                 type: 'line',
72842  *                 highlight: {
72843  *                     size: 7,
72844  *                     radius: 7
72845  *                 },
72846  *                 axis: 'left',
72847  *                 xField: 'name',
72848  *                 yField: 'data1',
72849  *                 markerConfig: {
72850  *                     type: 'cross',
72851  *                     size: 4,
72852  *                     radius: 4,
72853  *                     'stroke-width': 0
72854  *                 }
72855  *             },
72856  *             {
72857  *                 type: 'line',
72858  *                 highlight: {
72859  *                     size: 7,
72860  *                     radius: 7
72861  *                 },
72862  *                 axis: 'left',
72863  *                 fill: true,
72864  *                 xField: 'name',
72865  *                 yField: 'data2',
72866  *                 markerConfig: {
72867  *                     type: 'circle',
72868  *                     size: 4,
72869  *                     radius: 4,
72870  *                     'stroke-width': 0
72871  *                 }
72872  *             }
72873  *         ]
72874  *     });
72875  *
72876  * In this configuration we're adding two series (or lines), one bound to the `data1`
72877  * property of the store and the other to `data3`. The type for both configurations is
72878  * `line`. The `xField` for both series is the same, the name propert of the store.
72879  * Both line series share the same axis, the left axis. You can set particular marker
72880  * configuration by adding properties onto the markerConfig object. Both series have
72881  * an object as highlight so that markers animate smoothly to the properties in highlight
72882  * when hovered. The second series has `fill=true` which means that the line will also
72883  * have an area below it of the same color.
72884  *
72885  * **Note:** In the series definition remember to explicitly set the axis to bind the
72886  * values of the line series to. This can be done by using the `axis` configuration property.
72887  */
72888 Ext.define('Ext.chart.series.Line', {
72889
72890     /* Begin Definitions */
72891
72892     extend: 'Ext.chart.series.Cartesian',
72893
72894     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
72895
72896     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
72897
72898     /* End Definitions */
72899
72900     type: 'line',
72901
72902     alias: 'series.line',
72903
72904     /**
72905      * @cfg {String} axis
72906      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
72907      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
72908      * relative scale will be used.
72909      */
72910
72911     /**
72912      * @cfg {Number} selectionTolerance
72913      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
72914      */
72915     selectionTolerance: 20,
72916
72917     /**
72918      * @cfg {Boolean} showMarkers
72919      * Whether markers should be displayed at the data points along the line. If true,
72920      * then the {@link #markerConfig} config item will determine the markers' styling.
72921      */
72922     showMarkers: true,
72923
72924     /**
72925      * @cfg {Object} markerConfig
72926      * The display style for the markers. Only used if {@link #showMarkers} is true.
72927      * The markerConfig is a configuration object containing the same set of properties defined in
72928      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
72929      * pass the object:
72930      *
72931      <pre><code>
72932         markerConfig: {
72933             type: 'circle',
72934             radius: 4,
72935             'fill': '#f00'
72936         }
72937      </code></pre>
72938
72939      */
72940     markerConfig: {},
72941
72942     /**
72943      * @cfg {Object} style
72944      * An object containing style properties for the visualization lines and fill.
72945      * These styles will override the theme styles.  The following are valid style properties:
72946      *
72947      * - `stroke` - an rgb or hex color string for the background color of the line
72948      * - `stroke-width` - the width of the stroke (integer)
72949      * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
72950      * - `opacity` - the opacity of the line and the fill color (decimal)
72951      *
72952      * Example usage:
72953      *
72954      *     style: {
72955      *         stroke: '#00ff00',
72956      *         'stroke-width': 10,
72957      *         fill: '#80A080',
72958      *         opacity: 0.2
72959      *     }
72960      */
72961     style: {},
72962
72963     /**
72964      * @cfg {Boolean/Number} smooth
72965      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
72966      * straight line segments will be drawn.
72967      *
72968      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
72969      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
72970      *
72971      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
72972      */
72973     smooth: false,
72974
72975     /**
72976      * @private Default numeric smoothing value to be used when {@link #smooth} = true.
72977      */
72978     defaultSmoothness: 3,
72979
72980     /**
72981      * @cfg {Boolean} fill
72982      * If true, the area below the line will be filled in using the {@link #style eefill} and
72983      * {@link #style opacity} config properties. Defaults to false.
72984      */
72985     fill: false,
72986
72987     constructor: function(config) {
72988         this.callParent(arguments);
72989         var me = this,
72990             surface = me.chart.surface,
72991             shadow = me.chart.shadow,
72992             i, l;
72993         Ext.apply(me, config, {
72994             highlightCfg: {
72995                 'stroke-width': 3
72996             },
72997             shadowAttributes: [{
72998                 "stroke-width": 6,
72999                 "stroke-opacity": 0.05,
73000                 stroke: 'rgb(0, 0, 0)',
73001                 translate: {
73002                     x: 1,
73003                     y: 1
73004                 }
73005             }, {
73006                 "stroke-width": 4,
73007                 "stroke-opacity": 0.1,
73008                 stroke: 'rgb(0, 0, 0)',
73009                 translate: {
73010                     x: 1,
73011                     y: 1
73012                 }
73013             }, {
73014                 "stroke-width": 2,
73015                 "stroke-opacity": 0.15,
73016                 stroke: 'rgb(0, 0, 0)',
73017                 translate: {
73018                     x: 1,
73019                     y: 1
73020                 }
73021             }]
73022         });
73023         me.group = surface.getGroup(me.seriesId);
73024         if (me.showMarkers) {
73025             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
73026         }
73027         if (shadow) {
73028             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
73029                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
73030             }
73031         }
73032     },
73033
73034     // @private makes an average of points when there are more data points than pixels to be rendered.
73035     shrink: function(xValues, yValues, size) {
73036         // Start at the 2nd point...
73037         var len = xValues.length,
73038             ratio = Math.floor(len / size),
73039             i = 1,
73040             xSum = 0,
73041             ySum = 0,
73042             xRes = [xValues[0]],
73043             yRes = [yValues[0]];
73044
73045         for (; i < len; ++i) {
73046             xSum += xValues[i] || 0;
73047             ySum += yValues[i] || 0;
73048             if (i % ratio == 0) {
73049                 xRes.push(xSum/ratio);
73050                 yRes.push(ySum/ratio);
73051                 xSum = 0;
73052                 ySum = 0;
73053             }
73054         }
73055         return {
73056             x: xRes,
73057             y: yRes
73058         };
73059     },
73060
73061     /**
73062      * Draws the series for the current chart.
73063      */
73064     drawSeries: function() {
73065         var me = this,
73066             chart = me.chart,
73067             chartAxes = chart.axes,
73068             store = chart.getChartStore(),
73069             storeCount = store.getCount(),
73070             surface = me.chart.surface,
73071             bbox = {},
73072             group = me.group,
73073             showMarkers = me.showMarkers,
73074             markerGroup = me.markerGroup,
73075             enableShadows = chart.shadow,
73076             shadowGroups = me.shadowGroups,
73077             shadowAttributes = me.shadowAttributes,
73078             smooth = me.smooth,
73079             lnsh = shadowGroups.length,
73080             dummyPath = ["M"],
73081             path = ["M"],
73082             renderPath = ["M"],
73083             smoothPath = ["M"],
73084             markerIndex = chart.markerIndex,
73085             axes = [].concat(me.axis),
73086             shadowBarAttr,
73087             xValues = [],
73088             xValueMap = {},
73089             yValues = [],
73090             yValueMap = {},
73091             onbreak = false,
73092             storeIndices = [],
73093             markerStyle = me.markerStyle,
73094             seriesStyle = me.style,
73095             colorArrayStyle = me.colorArrayStyle,
73096             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
73097             isNumber = Ext.isNumber,
73098             seriesIdx = me.seriesIdx, 
73099             boundAxes = me.getAxesForXAndYFields(),
73100             boundXAxis = boundAxes.xAxis,
73101             boundYAxis = boundAxes.yAxis,
73102             shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
73103             x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
73104             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
73105             endLineStyle, type, count, items;
73106
73107         if (me.fireEvent('beforedraw', me) === false) {
73108             return;
73109         }
73110
73111         //if store is empty or the series is excluded in the legend then there's nothing to draw.
73112         if (!storeCount || me.seriesIsHidden) {
73113             items = this.items;
73114             if (items) {
73115                 for (i = 0, ln = items.length; i < ln; ++i) {
73116                     if (items[i].sprite) {
73117                         items[i].sprite.hide(true);
73118                     }
73119                 }
73120             }
73121             return;
73122         }
73123
73124         //prepare style objects for line and markers
73125         endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
73126         type = endMarkerStyle.type;
73127         delete endMarkerStyle.type;
73128         endLineStyle = seriesStyle;
73129         //if no stroke with is specified force it to 0.5 because this is
73130         //about making *lines*
73131         if (!endLineStyle['stroke-width']) {
73132             endLineStyle['stroke-width'] = 0.5;
73133         }
73134         //If we're using a time axis and we need to translate the points,
73135         //then reuse the first markers as the last markers.
73136         if (markerIndex && markerGroup && markerGroup.getCount()) {
73137             for (i = 0; i < markerIndex; i++) {
73138                 marker = markerGroup.getAt(i);
73139                 markerGroup.remove(marker);
73140                 markerGroup.add(marker);
73141                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
73142                 marker.setAttributes({
73143                     x: 0,
73144                     y: 0,
73145                     translate: {
73146                         x: markerAux.attr.translation.x,
73147                         y: markerAux.attr.translation.y
73148                     }
73149                 }, true);
73150             }
73151         }
73152
73153         me.unHighlightItem();
73154         me.cleanHighlights();
73155
73156         me.setBBox();
73157         bbox = me.bbox;
73158         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
73159         for (i = 0, ln = axes.length; i < ln; i++) {
73160             axis = chartAxes.get(axes[i]);
73161             if (axis) {
73162                 ends = axis.calcEnds();
73163                 if (axis.position == 'top' || axis.position == 'bottom') {
73164                     minX = ends.from;
73165                     maxX = ends.to;
73166                 }
73167                 else {
73168                     minY = ends.from;
73169                     maxY = ends.to;
73170                 }
73171             }
73172         }
73173         // If a field was specified without a corresponding axis, create one to get bounds
73174         //only do this for the axis where real values are bound (that's why we check for
73175         //me.axis)
73176         if (me.xField && !isNumber(minX) &&
73177             (boundXAxis == 'bottom' || boundXAxis == 'top') && 
73178             !chartAxes.get(boundXAxis)) {
73179             axis = Ext.create('Ext.chart.axis.Axis', {
73180                 chart: chart,
73181                 fields: [].concat(me.xField)
73182             }).calcEnds();
73183             minX = axis.from;
73184             maxX = axis.to;
73185         }
73186         if (me.yField && !isNumber(minY) &&
73187             (boundYAxis == 'right' || boundYAxis == 'left') &&
73188             !chartAxes.get(boundYAxis)) {
73189             axis = Ext.create('Ext.chart.axis.Axis', {
73190                 chart: chart,
73191                 fields: [].concat(me.yField)
73192             }).calcEnds();
73193             minY = axis.from;
73194             maxY = axis.to;
73195         }
73196         if (isNaN(minX)) {
73197             minX = 0;
73198             xScale = bbox.width / ((storeCount - 1) || 1);
73199         }
73200         else {
73201             xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
73202         }
73203
73204         if (isNaN(minY)) {
73205             minY = 0;
73206             yScale = bbox.height / ((storeCount - 1) || 1);
73207         }
73208         else {
73209             yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
73210         }
73211
73212         // Extract all x and y values from the store
73213         me.eachRecord(function(record, i) {
73214             xValue = record.get(me.xField);
73215
73216             // Ensure a value
73217             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
73218                 //set as uniform distribution if the axis is a category axis.
73219                 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
73220                     if (xValue in xValueMap) {
73221                         xValue = xValueMap[xValue];
73222                     } else {
73223                         xValue = xValueMap[xValue] = i;
73224                     }
73225             }
73226
73227             // Filter out values that don't fit within the pan/zoom buffer area
73228             yValue = record.get(me.yField);
73229             //skip undefined values
73230             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
73231                 if (Ext.isDefined(Ext.global.console)) {
73232                     Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ", record, xValue, yValue);
73233                 }
73234                 return;
73235             }
73236             // Ensure a value
73237             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
73238                 //set as uniform distribution if the axis is a category axis.
73239                 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
73240                 yValue = i;
73241             }
73242             storeIndices.push(i);
73243             xValues.push(xValue);
73244             yValues.push(yValue);
73245         });
73246
73247         ln = xValues.length;
73248         if (ln > bbox.width) {
73249             coords = me.shrink(xValues, yValues, bbox.width);
73250             xValues = coords.x;
73251             yValues = coords.y;
73252         }
73253
73254         me.items = [];
73255
73256         count = 0;
73257         ln = xValues.length;
73258         for (i = 0; i < ln; i++) {
73259             xValue = xValues[i];
73260             yValue = yValues[i];
73261             if (yValue === false) {
73262                 if (path.length == 1) {
73263                     path = [];
73264                 }
73265                 onbreak = true;
73266                 me.items.push(false);
73267                 continue;
73268             } else {
73269                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
73270                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
73271                 if (onbreak) {
73272                     onbreak = false;
73273                     path.push('M');
73274                 }
73275                 path = path.concat([x, y]);
73276             }
73277             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
73278                 firstY = y;
73279                 firstX = x;
73280             }
73281             // If this is the first line, create a dummypath to animate in from.
73282             if (!me.line || chart.resizing) {
73283                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
73284             }
73285
73286             // When resizing, reset before animating
73287             if (chart.animate && chart.resizing && me.line) {
73288                 me.line.setAttributes({
73289                     path: dummyPath
73290                 }, true);
73291                 if (me.fillPath) {
73292                     me.fillPath.setAttributes({
73293                         path: dummyPath,
73294                         opacity: 0.2
73295                     }, true);
73296                 }
73297                 if (me.line.shadows) {
73298                     shadows = me.line.shadows;
73299                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
73300                         shadow = shadows[j];
73301                         shadow.setAttributes({
73302                             path: dummyPath
73303                         }, true);
73304                     }
73305                 }
73306             }
73307             if (showMarkers) {
73308                 marker = markerGroup.getAt(count++);
73309                 if (!marker) {
73310                     marker = Ext.chart.Shape[type](surface, Ext.apply({
73311                         group: [group, markerGroup],
73312                         x: 0, y: 0,
73313                         translate: {
73314                             x: +(prevX || x),
73315                             y: prevY || (bbox.y + bbox.height / 2)
73316                         },
73317                         value: '"' + xValue + ', ' + yValue + '"',
73318                         zIndex: 4000
73319                     }, endMarkerStyle));
73320                     marker._to = {
73321                         translate: {
73322                             x: +x,
73323                             y: +y
73324                         }
73325                     };
73326                 } else {
73327                     marker.setAttributes({
73328                         value: '"' + xValue + ', ' + yValue + '"',
73329                         x: 0, y: 0,
73330                         hidden: false
73331                     }, true);
73332                     marker._to = {
73333                         translate: {
73334                             x: +x, 
73335                             y: +y
73336                         }
73337                     };
73338                 }
73339             }
73340             me.items.push({
73341                 series: me,
73342                 value: [xValue, yValue],
73343                 point: [x, y],
73344                 sprite: marker,
73345                 storeItem: store.getAt(storeIndices[i])
73346             });
73347             prevX = x;
73348             prevY = y;
73349         }
73350
73351         if (path.length <= 1) {
73352             //nothing to be rendered
73353             return;
73354         }
73355
73356         if (me.smooth) {
73357             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
73358         }
73359
73360         renderPath = smooth ? smoothPath : path;
73361
73362         //Correct path if we're animating timeAxis intervals
73363         if (chart.markerIndex && me.previousPath) {
73364             fromPath = me.previousPath;
73365             if (!smooth) {
73366                 Ext.Array.erase(fromPath, 1, 2);
73367             }
73368         } else {
73369             fromPath = path;
73370         }
73371
73372         // Only create a line if one doesn't exist.
73373         if (!me.line) {
73374             me.line = surface.add(Ext.apply({
73375                 type: 'path',
73376                 group: group,
73377                 path: dummyPath,
73378                 stroke: endLineStyle.stroke || endLineStyle.fill
73379             }, endLineStyle || {}));
73380
73381             if (enableShadows) {
73382                 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
73383             }
73384
73385             //unset fill here (there's always a default fill withing the themes).
73386             me.line.setAttributes({
73387                 fill: 'none',
73388                 zIndex: 3000
73389             });
73390             if (!endLineStyle.stroke && colorArrayLength) {
73391                 me.line.setAttributes({
73392                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
73393                 }, true);
73394             }
73395             if (enableShadows) {
73396                 //create shadows
73397                 shadows = me.line.shadows = [];
73398                 for (shindex = 0; shindex < lnsh; shindex++) {
73399                     shadowBarAttr = shadowAttributes[shindex];
73400                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
73401                     shadow = surface.add(Ext.apply({}, {
73402                         type: 'path',
73403                         group: shadowGroups[shindex]
73404                     }, shadowBarAttr));
73405                     shadows.push(shadow);
73406                 }
73407             }
73408         }
73409         if (me.fill) {
73410             fillPath = renderPath.concat([
73411                 ["L", x, bbox.y + bbox.height],
73412                 ["L", firstX, bbox.y + bbox.height],
73413                 ["L", firstX, firstY]
73414             ]);
73415             if (!me.fillPath) {
73416                 me.fillPath = surface.add({
73417                     group: group,
73418                     type: 'path',
73419                     opacity: endLineStyle.opacity || 0.3,
73420                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
73421                     path: dummyPath
73422                 });
73423             }
73424         }
73425         markerCount = showMarkers && markerGroup.getCount();
73426         if (chart.animate) {
73427             fill = me.fill;
73428             line = me.line;
73429             //Add renderer to line. There is not unique record associated with this.
73430             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
73431             Ext.apply(rendererAttributes, endLineStyle || {}, {
73432                 stroke: endLineStyle.stroke || endLineStyle.fill
73433             });
73434             //fill should not be used here but when drawing the special fill path object
73435             delete rendererAttributes.fill;
73436             line.show(true);
73437             if (chart.markerIndex && me.previousPath) {
73438                 me.animation = animation = me.onAnimate(line, {
73439                     to: rendererAttributes,
73440                     from: {
73441                         path: fromPath
73442                     }
73443                 });
73444             } else {
73445                 me.animation = animation = me.onAnimate(line, {
73446                     to: rendererAttributes
73447                 });
73448             }
73449             //animate shadows
73450             if (enableShadows) {
73451                 shadows = line.shadows;
73452                 for(j = 0; j < lnsh; j++) {
73453                     shadows[j].show(true);
73454                     if (chart.markerIndex && me.previousPath) {
73455                         me.onAnimate(shadows[j], {
73456                             to: { path: renderPath },
73457                             from: { path: fromPath }
73458                         });
73459                     } else {
73460                         me.onAnimate(shadows[j], {
73461                             to: { path: renderPath }
73462                         });
73463                     }
73464                 }
73465             }
73466             //animate fill path
73467             if (fill) {
73468                 me.fillPath.show(true);
73469                 me.onAnimate(me.fillPath, {
73470                     to: Ext.apply({}, {
73471                         path: fillPath,
73472                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
73473                         'stroke-width': 0
73474                     }, endLineStyle || {})
73475                 });
73476             }
73477             //animate markers
73478             if (showMarkers) {
73479                 count = 0;
73480                 for(i = 0; i < ln; i++) {
73481                     if (me.items[i]) {
73482                         item = markerGroup.getAt(count++);
73483                         if (item) {
73484                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
73485                             me.onAnimate(item, {
73486                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
73487                             });
73488                             item.show(true);
73489                         }
73490                     }
73491                 }
73492                 for(; count < markerCount; count++) {
73493                     item = markerGroup.getAt(count);
73494                     item.hide(true);
73495                 }
73496 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
73497 //                    item = markerGroup.getAt(i);
73498 //                    item.hide(true);
73499 //                }
73500             }
73501         } else {
73502             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
73503             Ext.apply(rendererAttributes, endLineStyle || {}, {
73504                 stroke: endLineStyle.stroke || endLineStyle.fill
73505             });
73506             //fill should not be used here but when drawing the special fill path object
73507             delete rendererAttributes.fill;
73508             me.line.setAttributes(rendererAttributes, true);
73509             //set path for shadows
73510             if (enableShadows) {
73511                 shadows = me.line.shadows;
73512                 for(j = 0; j < lnsh; j++) {
73513                     shadows[j].setAttributes({
73514                         path: renderPath,
73515                         hidden: false
73516                     }, true);
73517                 }
73518             }
73519             if (me.fill) {
73520                 me.fillPath.setAttributes({
73521                     path: fillPath,
73522                     hidden: false
73523                 }, true);
73524             }
73525             if (showMarkers) {
73526                 count = 0;
73527                 for(i = 0; i < ln; i++) {
73528                     if (me.items[i]) {
73529                         item = markerGroup.getAt(count++);
73530                         if (item) {
73531                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
73532                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
73533                             item.show(true);
73534                         }
73535                     }
73536                 }
73537                 for(; count < markerCount; count++) {
73538                     item = markerGroup.getAt(count);
73539                     item.hide(true);
73540                 }
73541             }
73542         }
73543
73544         if (chart.markerIndex) {
73545             if (me.smooth) {
73546                 Ext.Array.erase(path, 1, 2);
73547             } else {
73548                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
73549             }
73550             me.previousPath = path;
73551         }
73552         me.renderLabels();
73553         me.renderCallouts();
73554
73555         me.fireEvent('draw', me);
73556     },
73557
73558     // @private called when a label is to be created.
73559     onCreateLabel: function(storeItem, item, i, display) {
73560         var me = this,
73561             group = me.labelsGroup,
73562             config = me.label,
73563             bbox = me.bbox,
73564             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
73565
73566         return me.chart.surface.add(Ext.apply({
73567             'type': 'text',
73568             'text-anchor': 'middle',
73569             'group': group,
73570             'x': item.point[0],
73571             'y': bbox.y + bbox.height / 2
73572         }, endLabelStyle || {}));
73573     },
73574
73575     // @private called when a label is to be created.
73576     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
73577         var me = this,
73578             chart = me.chart,
73579             resizing = chart.resizing,
73580             config = me.label,
73581             format = config.renderer,
73582             field = config.field,
73583             bbox = me.bbox,
73584             x = item.point[0],
73585             y = item.point[1],
73586             radius = item.sprite.attr.radius,
73587             bb, width, height;
73588
73589         label.setAttributes({
73590             text: format(storeItem.get(field)),
73591             hidden: true
73592         }, true);
73593
73594         if (display == 'rotate') {
73595             label.setAttributes({
73596                 'text-anchor': 'start',
73597                 'rotation': {
73598                     x: x,
73599                     y: y,
73600                     degrees: -45
73601                 }
73602             }, true);
73603             //correct label position to fit into the box
73604             bb = label.getBBox();
73605             width = bb.width;
73606             height = bb.height;
73607             x = x < bbox.x? bbox.x : x;
73608             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
73609             y = (y - height < bbox.y)? bbox.y + height : y;
73610
73611         } else if (display == 'under' || display == 'over') {
73612             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
73613             bb = item.sprite.getBBox();
73614             bb.width = bb.width || (radius * 2);
73615             bb.height = bb.height || (radius * 2);
73616             y = y + (display == 'over'? -bb.height : bb.height);
73617             //correct label position to fit into the box
73618             bb = label.getBBox();
73619             width = bb.width/2;
73620             height = bb.height/2;
73621             x = x - width < bbox.x? bbox.x + width : x;
73622             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
73623             y = y - height < bbox.y? bbox.y + height : y;
73624             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
73625         }
73626
73627         if (me.chart.animate && !me.chart.resizing) {
73628             label.show(true);
73629             me.onAnimate(label, {
73630                 to: {
73631                     x: x,
73632                     y: y
73633                 }
73634             });
73635         } else {
73636             label.setAttributes({
73637                 x: x,
73638                 y: y
73639             }, true);
73640             if (resizing && me.animation) {
73641                 me.animation.on('afteranimate', function() {
73642                     label.show(true);
73643                 });
73644             } else {
73645                 label.show(true);
73646             }
73647         }
73648     },
73649
73650     //@private Overriding highlights.js highlightItem method.
73651     highlightItem: function() {
73652         var me = this;
73653         me.callParent(arguments);
73654         if (me.line && !me.highlighted) {
73655             if (!('__strokeWidth' in me.line)) {
73656                 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
73657             }
73658             if (me.line.__anim) {
73659                 me.line.__anim.paused = true;
73660             }
73661             me.line.__anim = Ext.create('Ext.fx.Anim', {
73662                 target: me.line,
73663                 to: {
73664                     'stroke-width': me.line.__strokeWidth + 3
73665                 }
73666             });
73667             me.highlighted = true;
73668         }
73669     },
73670
73671     //@private Overriding highlights.js unHighlightItem method.
73672     unHighlightItem: function() {
73673         var me = this;
73674         me.callParent(arguments);
73675         if (me.line && me.highlighted) {
73676             me.line.__anim = Ext.create('Ext.fx.Anim', {
73677                 target: me.line,
73678                 to: {
73679                     'stroke-width': me.line.__strokeWidth
73680                 }
73681             });
73682             me.highlighted = false;
73683         }
73684     },
73685
73686     //@private called when a callout needs to be placed.
73687     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
73688         if (!display) {
73689             return;
73690         }
73691
73692         var me = this,
73693             chart = me.chart,
73694             surface = chart.surface,
73695             resizing = chart.resizing,
73696             config = me.callouts,
73697             items = me.items,
73698             prev = i == 0? false : items[i -1].point,
73699             next = (i == items.length -1)? false : items[i +1].point,
73700             cur = [+item.point[0], +item.point[1]],
73701             dir, norm, normal, a, aprev, anext,
73702             offsetFromViz = config.offsetFromViz || 30,
73703             offsetToSide = config.offsetToSide || 10,
73704             offsetBox = config.offsetBox || 3,
73705             boxx, boxy, boxw, boxh,
73706             p, clipRect = me.clipRect,
73707             bbox = {
73708                 width: config.styles.width || 10,
73709                 height: config.styles.height || 10
73710             },
73711             x, y;
73712
73713         //get the right two points
73714         if (!prev) {
73715             prev = cur;
73716         }
73717         if (!next) {
73718             next = cur;
73719         }
73720         a = (next[1] - prev[1]) / (next[0] - prev[0]);
73721         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
73722         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
73723
73724         norm = Math.sqrt(1 + a * a);
73725         dir = [1 / norm, a / norm];
73726         normal = [-dir[1], dir[0]];
73727
73728         //keep the label always on the outer part of the "elbow"
73729         if (aprev > 0 && anext < 0 && normal[1] < 0
73730             || aprev < 0 && anext > 0 && normal[1] > 0) {
73731             normal[0] *= -1;
73732             normal[1] *= -1;
73733         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
73734                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
73735             normal[0] *= -1;
73736             normal[1] *= -1;
73737         }
73738         //position
73739         x = cur[0] + normal[0] * offsetFromViz;
73740         y = cur[1] + normal[1] * offsetFromViz;
73741
73742         //box position and dimensions
73743         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73744         boxy = y - bbox.height /2 - offsetBox;
73745         boxw = bbox.width + 2 * offsetBox;
73746         boxh = bbox.height + 2 * offsetBox;
73747
73748         //now check if we're out of bounds and invert the normal vector correspondingly
73749         //this may add new overlaps between labels (but labels won't be out of bounds).
73750         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
73751             normal[0] *= -1;
73752         }
73753         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
73754             normal[1] *= -1;
73755         }
73756
73757         //update positions
73758         x = cur[0] + normal[0] * offsetFromViz;
73759         y = cur[1] + normal[1] * offsetFromViz;
73760
73761         //update box position and dimensions
73762         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73763         boxy = y - bbox.height /2 - offsetBox;
73764         boxw = bbox.width + 2 * offsetBox;
73765         boxh = bbox.height + 2 * offsetBox;
73766
73767         if (chart.animate) {
73768             //set the line from the middle of the pie to the box.
73769             me.onAnimate(callout.lines, {
73770                 to: {
73771                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73772                 }
73773             });
73774             //set component position
73775             if (callout.panel) {
73776                 callout.panel.setPosition(boxx, boxy, true);
73777             }
73778         }
73779         else {
73780             //set the line from the middle of the pie to the box.
73781             callout.lines.setAttributes({
73782                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73783             }, true);
73784             //set component position
73785             if (callout.panel) {
73786                 callout.panel.setPosition(boxx, boxy);
73787             }
73788         }
73789         for (p in callout) {
73790             callout[p].show(true);
73791         }
73792     },
73793
73794     isItemInPoint: function(x, y, item, i) {
73795         var me = this,
73796             items = me.items,
73797             tolerance = me.selectionTolerance,
73798             result = null,
73799             prevItem,
73800             nextItem,
73801             prevPoint,
73802             nextPoint,
73803             ln,
73804             x1,
73805             y1,
73806             x2,
73807             y2,
73808             xIntersect,
73809             yIntersect,
73810             dist1, dist2, dist, midx, midy,
73811             sqrt = Math.sqrt, abs = Math.abs;
73812
73813         nextItem = items[i];
73814         prevItem = i && items[i - 1];
73815
73816         if (i >= ln) {
73817             prevItem = items[ln - 1];
73818         }
73819         prevPoint = prevItem && prevItem.point;
73820         nextPoint = nextItem && nextItem.point;
73821         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
73822         y1 = prevItem ? prevPoint[1] : nextPoint[1];
73823         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
73824         y2 = nextItem ? nextPoint[1] : prevPoint[1];
73825         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
73826         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
73827         dist = Math.min(dist1, dist2);
73828
73829         if (dist <= tolerance) {
73830             return dist == dist1? prevItem : nextItem;
73831         }
73832         return false;
73833     },
73834
73835     // @private toggle visibility of all series elements (markers, sprites).
73836     toggleAll: function(show) {
73837         var me = this,
73838             i, ln, shadow, shadows;
73839         if (!show) {
73840             Ext.chart.series.Cartesian.prototype.hideAll.call(me);
73841         }
73842         else {
73843             Ext.chart.series.Cartesian.prototype.showAll.call(me);
73844         }
73845         if (me.line) {
73846             me.line.setAttributes({
73847                 hidden: !show
73848             }, true);
73849             //hide shadows too
73850             if (me.line.shadows) {
73851                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
73852                     shadow = shadows[i];
73853                     shadow.setAttributes({
73854                         hidden: !show
73855                     }, true);
73856                 }
73857             }
73858         }
73859         if (me.fillPath) {
73860             me.fillPath.setAttributes({
73861                 hidden: !show
73862             }, true);
73863         }
73864     },
73865
73866     // @private hide all series elements (markers, sprites).
73867     hideAll: function() {
73868         this.toggleAll(false);
73869     },
73870
73871     // @private hide all series elements (markers, sprites).
73872     showAll: function() {
73873         this.toggleAll(true);
73874     }
73875 });
73876
73877 /**
73878  * @class Ext.chart.series.Pie
73879  * @extends Ext.chart.series.Series
73880  *
73881  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
73882  * categories that also have a meaning as a whole.
73883  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
73884  * documentation for more information. A typical configuration object for the pie series could be:
73885  *
73886  *     @example
73887  *     var store = Ext.create('Ext.data.JsonStore', {
73888  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
73889  *         data: [
73890  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
73891  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
73892  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
73893  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
73894  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
73895  *         ]
73896  *     });
73897  *
73898  *     Ext.create('Ext.chart.Chart', {
73899  *         renderTo: Ext.getBody(),
73900  *         width: 500,
73901  *         height: 350,
73902  *         animate: true,
73903  *         store: store,
73904  *         theme: 'Base:gradients',
73905  *         series: [{
73906  *             type: 'pie',
73907  *             field: 'data1',
73908  *             showInLegend: true,
73909  *             tips: {
73910  *                 trackMouse: true,
73911  *                 width: 140,
73912  *                 height: 28,
73913  *                 renderer: function(storeItem, item) {
73914  *                     // calculate and display percentage on hover
73915  *                     var total = 0;
73916  *                     store.each(function(rec) {
73917  *                         total += rec.get('data1');
73918  *                     });
73919  *                     this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
73920  *                 }
73921  *             },
73922  *             highlight: {
73923  *                 segment: {
73924  *                     margin: 20
73925  *                 }
73926  *             },
73927  *             label: {
73928  *                 field: 'name',
73929  *                 display: 'rotate',
73930  *                 contrast: true,
73931  *                 font: '18px Arial'
73932  *             }
73933  *         }]
73934  *     });
73935  *
73936  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
73937  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
73938  *
73939  * 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
73940  * 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.
73941  *
73942  * 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
73943  * and size through the `font` parameter.
73944  *
73945  * @xtype pie
73946  */
73947 Ext.define('Ext.chart.series.Pie', {
73948
73949     /* Begin Definitions */
73950
73951     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
73952
73953     extend: 'Ext.chart.series.Series',
73954
73955     /* End Definitions */
73956
73957     type: "pie",
73958
73959     alias: 'series.pie',
73960
73961     rad: Math.PI / 180,
73962
73963     /**
73964      * @cfg {Number} highlightDuration
73965      * The duration for the pie slice highlight effect.
73966      */
73967     highlightDuration: 150,
73968
73969     /**
73970      * @cfg {String} angleField (required)
73971      * The store record field name to be used for the pie angles.
73972      * The values bound to this field name must be positive real numbers.
73973      */
73974     angleField: false,
73975
73976     /**
73977      * @cfg {String} lengthField
73978      * The store record field name to be used for the pie slice lengths.
73979      * The values bound to this field name must be positive real numbers.
73980      */
73981     lengthField: false,
73982
73983     /**
73984      * @cfg {Boolean/Number} donut
73985      * Whether to set the pie chart as donut chart.
73986      * Default's false. Can be set to a particular percentage to set the radius
73987      * of the donut chart.
73988      */
73989     donut: false,
73990
73991     /**
73992      * @cfg {Boolean} showInLegend
73993      * Whether to add the pie chart elements as legend items. Default's false.
73994      */
73995     showInLegend: false,
73996
73997     /**
73998      * @cfg {Array} colorSet
73999      * An array of color values which will be used, in order, as the pie slice fill colors.
74000      */
74001
74002     /**
74003      * @cfg {Object} style
74004      * An object containing styles for overriding series styles from Theming.
74005      */
74006     style: {},
74007
74008     constructor: function(config) {
74009         this.callParent(arguments);
74010         var me = this,
74011             chart = me.chart,
74012             surface = chart.surface,
74013             store = chart.store,
74014             shadow = chart.shadow, i, l, cfg;
74015         Ext.applyIf(me, {
74016             highlightCfg: {
74017                 segment: {
74018                     margin: 20
74019                 }
74020             }
74021         });
74022         Ext.apply(me, config, {
74023             shadowAttributes: [{
74024                 "stroke-width": 6,
74025                 "stroke-opacity": 1,
74026                 stroke: 'rgb(200, 200, 200)',
74027                 translate: {
74028                     x: 1.2,
74029                     y: 2
74030                 }
74031             },
74032             {
74033                 "stroke-width": 4,
74034                 "stroke-opacity": 1,
74035                 stroke: 'rgb(150, 150, 150)',
74036                 translate: {
74037                     x: 0.9,
74038                     y: 1.5
74039                 }
74040             },
74041             {
74042                 "stroke-width": 2,
74043                 "stroke-opacity": 1,
74044                 stroke: 'rgb(100, 100, 100)',
74045                 translate: {
74046                     x: 0.6,
74047                     y: 1
74048                 }
74049             }]
74050         });
74051         me.group = surface.getGroup(me.seriesId);
74052         if (shadow) {
74053             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
74054                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
74055             }
74056         }
74057         surface.customAttributes.segment = function(opt) {
74058             return me.getSegment(opt);
74059         };
74060         me.__excludes = me.__excludes || [];
74061     },
74062
74063     //@private updates some onbefore render parameters.
74064     initialize: function() {
74065         var me = this,
74066             store = me.chart.getChartStore();
74067         //Add yFields to be used in Legend.js
74068         me.yField = [];
74069         if (me.label.field) {
74070             store.each(function(rec) {
74071                 me.yField.push(rec.get(me.label.field));
74072             });
74073         }
74074     },
74075
74076     // @private returns an object with properties for a PieSlice.
74077     getSegment: function(opt) {
74078         var me = this,
74079             rad = me.rad,
74080             cos = Math.cos,
74081             sin = Math.sin,
74082             x = me.centerX,
74083             y = me.centerY,
74084             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
74085             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
74086             x5 = 0, y5 = 0, x6 = 0, y6 = 0,
74087             delta = 1e-2,
74088             startAngle = opt.startAngle,
74089             endAngle = opt.endAngle,
74090             midAngle = (startAngle + endAngle) / 2 * rad,
74091             margin = opt.margin || 0,
74092             a1 = Math.min(startAngle, endAngle) * rad,
74093             a2 = Math.max(startAngle, endAngle) * rad,
74094             c1 = cos(a1), s1 = sin(a1),
74095             c2 = cos(a2), s2 = sin(a2),
74096             cm = cos(midAngle), sm = sin(midAngle),
74097             flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
74098
74099         if (a2 - a1 < delta) {
74100             return {path: ""};
74101         }
74102
74103         if (margin !== 0) {
74104             x += margin * cm;
74105             y += margin * sm;
74106         }
74107
74108         x2 = x + opt.endRho * c1;
74109         y2 = y + opt.endRho * s1;
74110
74111         x4 = x + opt.endRho * c2;
74112         y4 = y + opt.endRho * s2;
74113
74114         if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
74115             cm = hsqr2;
74116             sm = -hsqr2;
74117             flag = 1;
74118         }
74119
74120         x6 = x + opt.endRho * cm;
74121         y6 = y + opt.endRho * sm;
74122
74123         // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
74124         // Better fix the VML engine for half circles.
74125
74126         if (opt.startRho !== 0) {
74127             x1 = x + opt.startRho * c1;
74128             y1 = y + opt.startRho * s1;
74129     
74130             x3 = x + opt.startRho * c2;
74131             y3 = y + opt.startRho * s2;
74132     
74133             x5 = x + opt.startRho * cm;
74134             y5 = y + opt.startRho * sm;
74135
74136             return {
74137                 path: [
74138                     ["M", x2, y2],
74139                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
74140                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
74141                     ["L", x3, y3],
74142                     ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
74143                     ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
74144                     ["Z"]
74145                 ]
74146             };
74147         } else {
74148             return {
74149                 path: [
74150                     ["M", x, y],
74151                     ["L", x2, y2],
74152                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
74153                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
74154                     ["L", x, y],
74155                     ["Z"]
74156                 ]
74157             };
74158         }
74159     },
74160
74161     // @private utility function to calculate the middle point of a pie slice.
74162     calcMiddle: function(item) {
74163         var me = this,
74164             rad = me.rad,
74165             slice = item.slice,
74166             x = me.centerX,
74167             y = me.centerY,
74168             startAngle = slice.startAngle,
74169             endAngle = slice.endAngle,
74170             donut = +me.donut,
74171             midAngle = -(startAngle + endAngle) * rad / 2,
74172             r = (item.endRho + item.startRho) / 2,
74173             xm = x + r * Math.cos(midAngle),
74174             ym = y - r * Math.sin(midAngle);
74175
74176         item.middle = {
74177             x: xm,
74178             y: ym
74179         };
74180     },
74181
74182     /**
74183      * Draws the series for the current chart.
74184      */
74185     drawSeries: function() {
74186         var me = this,
74187             store = me.chart.getChartStore(),
74188             group = me.group,
74189             animate = me.chart.animate,
74190             field = me.angleField || me.field || me.xField,
74191             lenField = [].concat(me.lengthField),
74192             totalLenField = 0,
74193             colors = me.colorSet,
74194             chart = me.chart,
74195             surface = chart.surface,
74196             chartBBox = chart.chartBBox,
74197             enableShadows = chart.shadow,
74198             shadowGroups = me.shadowGroups,
74199             shadowAttributes = me.shadowAttributes,
74200             lnsh = shadowGroups.length,
74201             rad = me.rad,
74202             layers = lenField.length,
74203             rhoAcum = 0,
74204             donut = +me.donut,
74205             layerTotals = [],
74206             values = {},
74207             fieldLength,
74208             items = [],
74209             passed = false,
74210             totalField = 0,
74211             maxLenField = 0,
74212             cut = 9,
74213             defcut = true,
74214             angle = 0,
74215             seriesStyle = me.seriesStyle,
74216             seriesLabelStyle = me.seriesLabelStyle,
74217             colorArrayStyle = me.colorArrayStyle,
74218             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
74219             gutterX = chart.maxGutter[0],
74220             gutterY = chart.maxGutter[1],
74221             abs = Math.abs,
74222             rendererAttributes,
74223             shadowGroup,
74224             shadowAttr,
74225             shadows,
74226             shadow,
74227             shindex,
74228             centerX,
74229             centerY,
74230             deltaRho,
74231             first = 0,
74232             slice,
74233             slices,
74234             sprite,
74235             value,
74236             item,
74237             lenValue,
74238             ln,
74239             record,
74240             i,
74241             j,
74242             startAngle,
74243             endAngle,
74244             middleAngle,
74245             sliceLength,
74246             path,
74247             p,
74248             spriteOptions, bbox;
74249
74250         Ext.apply(seriesStyle, me.style || {});
74251
74252         me.setBBox();
74253         bbox = me.bbox;
74254
74255         //override theme colors
74256         if (me.colorSet) {
74257             colorArrayStyle = me.colorSet;
74258             colorArrayLength = colorArrayStyle.length;
74259         }
74260
74261         //if not store or store is empty then there's nothing to draw
74262         if (!store || !store.getCount()) {
74263             return;
74264         }
74265
74266         me.unHighlightItem();
74267         me.cleanHighlights();
74268
74269         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
74270         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
74271         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
74272         me.slices = slices = [];
74273         me.items = items = [];
74274
74275         store.each(function(record, i) {
74276             if (this.__excludes && this.__excludes[i]) {
74277                 //hidden series
74278                 return;
74279             }
74280             totalField += +record.get(field);
74281             if (lenField[0]) {
74282                 for (j = 0, totalLenField = 0; j < layers; j++) {
74283                     totalLenField += +record.get(lenField[j]);
74284                 }
74285                 layerTotals[i] = totalLenField;
74286                 maxLenField = Math.max(maxLenField, totalLenField);
74287             }
74288         }, this);
74289
74290         totalField = totalField || 1;
74291         store.each(function(record, i) {
74292             if (this.__excludes && this.__excludes[i]) {
74293                 value = 0;
74294             } else {
74295                 value = record.get(field);
74296                 if (first == 0) {
74297                     first = 1;
74298                 }
74299             }
74300
74301             // First slice
74302             if (first == 1) {
74303                 first = 2;
74304                 me.firstAngle = angle = 360 * value / totalField / 2;
74305                 for (j = 0; j < i; j++) {
74306                     slices[j].startAngle = slices[j].endAngle = me.firstAngle;
74307                 }
74308             }
74309             
74310             endAngle = angle - 360 * value / totalField;
74311             slice = {
74312                 series: me,
74313                 value: value,
74314                 startAngle: angle,
74315                 endAngle: endAngle,
74316                 storeItem: record
74317             };
74318             if (lenField[0]) {
74319                 lenValue = layerTotals[i];
74320                 slice.rho = me.radius * (lenValue / maxLenField);
74321             } else {
74322                 slice.rho = me.radius;
74323             }
74324             slices[i] = slice;
74325             angle = endAngle;
74326         }, me);
74327         //do all shadows first.
74328         if (enableShadows) {
74329             for (i = 0, ln = slices.length; i < ln; i++) {
74330                 slice = slices[i];
74331                 slice.shadowAttrs = [];
74332                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
74333                     sprite = group.getAt(i * layers + j);
74334                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
74335                     //set pie slice properties
74336                     rendererAttributes = {
74337                         segment: {
74338                             startAngle: slice.startAngle,
74339                             endAngle: slice.endAngle,
74340                             margin: 0,
74341                             rho: slice.rho,
74342                             startRho: rhoAcum + (deltaRho * donut / 100),
74343                             endRho: rhoAcum + deltaRho
74344                         },
74345                         hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
74346                     };
74347                     //create shadows
74348                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
74349                         shadowAttr = shadowAttributes[shindex];
74350                         shadow = shadowGroups[shindex].getAt(i);
74351                         if (!shadow) {
74352                             shadow = chart.surface.add(Ext.apply({}, {
74353                                 type: 'path',
74354                                 group: shadowGroups[shindex],
74355                                 strokeLinejoin: "round"
74356                             }, rendererAttributes, shadowAttr));
74357                         }
74358                         if (animate) {
74359                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
74360                             me.onAnimate(shadow, {
74361                                 to: shadowAttr
74362                             });
74363                         } else {
74364                             shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
74365                             shadow.setAttributes(shadowAttr, true);
74366                         }
74367                         shadows.push(shadow);
74368                     }
74369                     slice.shadowAttrs[j] = shadows;
74370                 }
74371             }
74372         }
74373         //do pie slices after.
74374         for (i = 0, ln = slices.length; i < ln; i++) {
74375             slice = slices[i];
74376             for (j = 0, rhoAcum = 0; j < layers; j++) {
74377                 sprite = group.getAt(i * layers + j);
74378                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
74379                 //set pie slice properties
74380                 rendererAttributes = Ext.apply({
74381                     segment: {
74382                         startAngle: slice.startAngle,
74383                         endAngle: slice.endAngle,
74384                         margin: 0,
74385                         rho: slice.rho,
74386                         startRho: rhoAcum + (deltaRho * donut / 100),
74387                         endRho: rhoAcum + deltaRho
74388                     },
74389                     hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
74390                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
74391                 item = Ext.apply({},
74392                 rendererAttributes.segment, {
74393                     slice: slice,
74394                     series: me,
74395                     storeItem: slice.storeItem,
74396                     index: i
74397                 });
74398                 me.calcMiddle(item);
74399                 if (enableShadows) {
74400                     item.shadows = slice.shadowAttrs[j];
74401                 }
74402                 items[i] = item;
74403                 // Create a new sprite if needed (no height)
74404                 if (!sprite) {
74405                     spriteOptions = Ext.apply({
74406                         type: "path",
74407                         group: group,
74408                         middle: item.middle
74409                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
74410                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
74411                 }
74412                 slice.sprite = slice.sprite || [];
74413                 item.sprite = sprite;
74414                 slice.sprite.push(sprite);
74415                 slice.point = [item.middle.x, item.middle.y];
74416                 if (animate) {
74417                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
74418                     sprite._to = rendererAttributes;
74419                     sprite._animating = true;
74420                     me.onAnimate(sprite, {
74421                         to: rendererAttributes,
74422                         listeners: {
74423                             afteranimate: {
74424                                 fn: function() {
74425                                     this._animating = false;
74426                                 },
74427                                 scope: sprite
74428                             }
74429                         }
74430                     });
74431                 } else {
74432                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
74433                         hidden: false
74434                     }), i, store);
74435                     sprite.setAttributes(rendererAttributes, true);
74436                 }
74437                 rhoAcum += deltaRho;
74438             }
74439         }
74440
74441         // Hide unused bars
74442         ln = group.getCount();
74443         for (i = 0; i < ln; i++) {
74444             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
74445                 group.getAt(i).hide(true);
74446             }
74447         }
74448         if (enableShadows) {
74449             lnsh = shadowGroups.length;
74450             for (shindex = 0; shindex < ln; shindex++) {
74451                 if (!slices[(shindex / layers) >> 0]) {
74452                     for (j = 0; j < lnsh; j++) {
74453                         if (shadowGroups[j].getAt(shindex)) {
74454                             shadowGroups[j].getAt(shindex).hide(true);
74455                         }
74456                     }
74457                 }
74458             }
74459         }
74460         me.renderLabels();
74461         me.renderCallouts();
74462     },
74463
74464     // @private callback for when creating a label sprite.
74465     onCreateLabel: function(storeItem, item, i, display) {
74466         var me = this,
74467             group = me.labelsGroup,
74468             config = me.label,
74469             centerX = me.centerX,
74470             centerY = me.centerY,
74471             middle = item.middle,
74472             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
74473
74474         return me.chart.surface.add(Ext.apply({
74475             'type': 'text',
74476             'text-anchor': 'middle',
74477             'group': group,
74478             'x': middle.x,
74479             'y': middle.y
74480         }, endLabelStyle));
74481     },
74482
74483     // @private callback for when placing a label sprite.
74484     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
74485         var me = this,
74486             chart = me.chart,
74487             resizing = chart.resizing,
74488             config = me.label,
74489             format = config.renderer,
74490             field = [].concat(config.field),
74491             centerX = me.centerX,
74492             centerY = me.centerY,
74493             middle = item.middle,
74494             opt = {
74495                 x: middle.x,
74496                 y: middle.y
74497             },
74498             x = middle.x - centerX,
74499             y = middle.y - centerY,
74500             from = {},
74501             rho = 1,
74502             theta = Math.atan2(y, x || 1),
74503             dg = theta * 180 / Math.PI,
74504             prevDg;
74505         if (this.__excludes && this.__excludes[i]) {
74506             opt.hidden = true;
74507         }
74508         function fixAngle(a) {
74509             if (a < 0) {
74510                 a += 360;
74511             }
74512             return a % 360;
74513         }
74514
74515         label.setAttributes({
74516             text: format(storeItem.get(field[index]))
74517         }, true);
74518
74519         switch (display) {
74520         case 'outside':
74521             rho = Math.sqrt(x * x + y * y) * 2;
74522             //update positions
74523             opt.x = rho * Math.cos(theta) + centerX;
74524             opt.y = rho * Math.sin(theta) + centerY;
74525             break;
74526
74527         case 'rotate':
74528             dg = fixAngle(dg);
74529             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
74530
74531             prevDg = label.attr.rotation.degrees;
74532             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
74533                 if (dg > prevDg) {
74534                     dg -= 360;
74535                 } else {
74536                     dg += 360;
74537                 }
74538                 dg = dg % 360;
74539             } else {
74540                 dg = fixAngle(dg);
74541             }
74542             //update rotation angle
74543             opt.rotate = {
74544                 degrees: dg,
74545                 x: opt.x,
74546                 y: opt.y
74547             };
74548             break;
74549
74550         default:
74551             break;
74552         }
74553         //ensure the object has zero translation
74554         opt.translate = {
74555             x: 0, y: 0
74556         };
74557         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
74558             me.onAnimate(label, {
74559                 to: opt
74560             });
74561         } else {
74562             label.setAttributes(opt, true);
74563         }
74564         label._from = from;
74565     },
74566
74567     // @private callback for when placing a callout sprite.
74568     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
74569         var me = this,
74570             chart = me.chart,
74571             resizing = chart.resizing,
74572             config = me.callouts,
74573             centerX = me.centerX,
74574             centerY = me.centerY,
74575             middle = item.middle,
74576             opt = {
74577                 x: middle.x,
74578                 y: middle.y
74579             },
74580             x = middle.x - centerX,
74581             y = middle.y - centerY,
74582             rho = 1,
74583             rhoCenter,
74584             theta = Math.atan2(y, x || 1),
74585             bbox = callout.label.getBBox(),
74586             offsetFromViz = 20,
74587             offsetToSide = 10,
74588             offsetBox = 10,
74589             p;
74590
74591         //should be able to config this.
74592         rho = item.endRho + offsetFromViz;
74593         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
74594         //update positions
74595         opt.x = rho * Math.cos(theta) + centerX;
74596         opt.y = rho * Math.sin(theta) + centerY;
74597
74598         x = rhoCenter * Math.cos(theta);
74599         y = rhoCenter * Math.sin(theta);
74600
74601         if (chart.animate) {
74602             //set the line from the middle of the pie to the box.
74603             me.onAnimate(callout.lines, {
74604                 to: {
74605                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
74606                 }
74607             });
74608             //set box position
74609             me.onAnimate(callout.box, {
74610                 to: {
74611                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
74612                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
74613                     width: bbox.width + 2 * offsetBox,
74614                     height: bbox.height + 2 * offsetBox
74615                 }
74616             });
74617             //set text position
74618             me.onAnimate(callout.label, {
74619                 to: {
74620                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
74621                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
74622                 }
74623             });
74624         } else {
74625             //set the line from the middle of the pie to the box.
74626             callout.lines.setAttributes({
74627                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
74628             },
74629             true);
74630             //set box position
74631             callout.box.setAttributes({
74632                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
74633                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
74634                 width: bbox.width + 2 * offsetBox,
74635                 height: bbox.height + 2 * offsetBox
74636             },
74637             true);
74638             //set text position
74639             callout.label.setAttributes({
74640                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
74641                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
74642             },
74643             true);
74644         }
74645         for (p in callout) {
74646             callout[p].show(true);
74647         }
74648     },
74649
74650     // @private handles sprite animation for the series.
74651     onAnimate: function(sprite, attr) {
74652         sprite.show();
74653         return this.callParent(arguments);
74654     },
74655
74656     isItemInPoint: function(x, y, item, i) {
74657         var me = this,
74658             cx = me.centerX,
74659             cy = me.centerY,
74660             abs = Math.abs,
74661             dx = abs(x - cx),
74662             dy = abs(y - cy),
74663             startAngle = item.startAngle,
74664             endAngle = item.endAngle,
74665             rho = Math.sqrt(dx * dx + dy * dy),
74666             angle = Math.atan2(y - cy, x - cx) / me.rad;
74667
74668         // normalize to the same range of angles created by drawSeries
74669         if (angle > me.firstAngle) {
74670             angle -= 360;
74671         }
74672         return (angle <= startAngle && angle > endAngle
74673                 && rho >= item.startRho && rho <= item.endRho);
74674     },
74675
74676     // @private hides all elements in the series.
74677     hideAll: function() {
74678         var i, l, shadow, shadows, sh, lsh, sprite;
74679         if (!isNaN(this._index)) {
74680             this.__excludes = this.__excludes || [];
74681             this.__excludes[this._index] = true;
74682             sprite = this.slices[this._index].sprite;
74683             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
74684                 sprite[sh].setAttributes({
74685                     hidden: true
74686                 }, true);
74687             }
74688             if (this.slices[this._index].shadowAttrs) {
74689                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
74690                     shadow = shadows[i];
74691                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
74692                         shadow[sh].setAttributes({
74693                             hidden: true
74694                         }, true);
74695                     }
74696                 }
74697             }
74698             this.drawSeries();
74699         }
74700     },
74701
74702     // @private shows all elements in the series.
74703     showAll: function() {
74704         if (!isNaN(this._index)) {
74705             this.__excludes[this._index] = false;
74706             this.drawSeries();
74707         }
74708     },
74709
74710     /**
74711      * Highlight the specified item. If no item is provided the whole series will be highlighted.
74712      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74713      */
74714     highlightItem: function(item) {
74715         var me = this,
74716             rad = me.rad;
74717         item = item || this.items[this._index];
74718
74719         //TODO(nico): sometimes in IE itemmouseover is triggered
74720         //twice without triggering itemmouseout in between. This
74721         //fixes the highlighting bug. Eventually, events should be
74722         //changed to trigger one itemmouseout between two itemmouseovers.
74723         this.unHighlightItem();
74724
74725         if (!item || item.sprite && item.sprite._animating) {
74726             return;
74727         }
74728         me.callParent([item]);
74729         if (!me.highlight) {
74730             return;
74731         }
74732         if ('segment' in me.highlightCfg) {
74733             var highlightSegment = me.highlightCfg.segment,
74734                 animate = me.chart.animate,
74735                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
74736             //animate labels
74737             if (me.labelsGroup) {
74738                 var group = me.labelsGroup,
74739                     display = me.label.display,
74740                     label = group.getAt(item.index),
74741                     middle = (item.startAngle + item.endAngle) / 2 * rad,
74742                     r = highlightSegment.margin || 0,
74743                     x = r * Math.cos(middle),
74744                     y = r * Math.sin(middle);
74745
74746                 //TODO(nico): rounding to 1e-10
74747                 //gives the right translation. Translation
74748                 //was buggy for very small numbers. In this
74749                 //case we're not looking to translate to very small
74750                 //numbers but not to translate at all.
74751                 if (Math.abs(x) < 1e-10) {
74752                     x = 0;
74753                 }
74754                 if (Math.abs(y) < 1e-10) {
74755                     y = 0;
74756                 }
74757
74758                 if (animate) {
74759                     label.stopAnimation();
74760                     label.animate({
74761                         to: {
74762                             translate: {
74763                                 x: x,
74764                                 y: y
74765                             }
74766                         },
74767                         duration: me.highlightDuration
74768                     });
74769                 }
74770                 else {
74771                     label.setAttributes({
74772                         translate: {
74773                             x: x,
74774                             y: y
74775                         }
74776                     }, true);
74777                 }
74778             }
74779             //animate shadows
74780             if (me.chart.shadow && item.shadows) {
74781                 i = 0;
74782                 shadows = item.shadows;
74783                 ln = shadows.length;
74784                 for (; i < ln; i++) {
74785                     shadow = shadows[i];
74786                     to = {};
74787                     itemHighlightSegment = item.sprite._from.segment;
74788                     for (prop in itemHighlightSegment) {
74789                         if (! (prop in highlightSegment)) {
74790                             to[prop] = itemHighlightSegment[prop];
74791                         }
74792                     }
74793                     attrs = {
74794                         segment: Ext.applyIf(to, me.highlightCfg.segment)
74795                     };
74796                     if (animate) {
74797                         shadow.stopAnimation();
74798                         shadow.animate({
74799                             to: attrs,
74800                             duration: me.highlightDuration
74801                         });
74802                     }
74803                     else {
74804                         shadow.setAttributes(attrs, true);
74805                     }
74806                 }
74807             }
74808         }
74809     },
74810
74811     /**
74812      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
74813      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74814      */
74815     unHighlightItem: function() {
74816         var me = this;
74817         if (!me.highlight) {
74818             return;
74819         }
74820
74821         if (('segment' in me.highlightCfg) && me.items) {
74822             var items = me.items,
74823                 animate = me.chart.animate,
74824                 shadowsEnabled = !!me.chart.shadow,
74825                 group = me.labelsGroup,
74826                 len = items.length,
74827                 i = 0,
74828                 j = 0,
74829                 display = me.label.display,
74830                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
74831
74832             for (; i < len; i++) {
74833                 item = items[i];
74834                 if (!item) {
74835                     continue;
74836                 }
74837                 sprite = item.sprite;
74838                 if (sprite && sprite._highlighted) {
74839                     //animate labels
74840                     if (group) {
74841                         label = group.getAt(item.index);
74842                         attrs = Ext.apply({
74843                             translate: {
74844                                 x: 0,
74845                                 y: 0
74846                             }
74847                         },
74848                         display == 'rotate' ? {
74849                             rotate: {
74850                                 x: label.attr.x,
74851                                 y: label.attr.y,
74852                                 degrees: label.attr.rotation.degrees
74853                             }
74854                         }: {});
74855                         if (animate) {
74856                             label.stopAnimation();
74857                             label.animate({
74858                                 to: attrs,
74859                                 duration: me.highlightDuration
74860                             });
74861                         }
74862                         else {
74863                             label.setAttributes(attrs, true);
74864                         }
74865                     }
74866                     if (shadowsEnabled) {
74867                         shadows = item.shadows;
74868                         shadowLen = shadows.length;
74869                         for (; j < shadowLen; j++) {
74870                             to = {};
74871                             ihs = item.sprite._to.segment;
74872                             hs = item.sprite._from.segment;
74873                             Ext.apply(to, hs);
74874                             for (p in ihs) {
74875                                 if (! (p in hs)) {
74876                                     to[p] = ihs[p];
74877                                 }
74878                             }
74879                             shadow = shadows[j];
74880                             if (animate) {
74881                                 shadow.stopAnimation();
74882                                 shadow.animate({
74883                                     to: {
74884                                         segment: to
74885                                     },
74886                                     duration: me.highlightDuration
74887                                 });
74888                             }
74889                             else {
74890                                 shadow.setAttributes({ segment: to }, true);
74891                             }
74892                         }
74893                     }
74894                 }
74895             }
74896         }
74897         me.callParent(arguments);
74898     },
74899
74900     /**
74901      * Returns the color of the series (to be displayed as color for the series legend item).
74902      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74903      */
74904     getLegendColor: function(index) {
74905         var me = this;
74906         return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
74907     }
74908 });
74909
74910
74911 /**
74912  * @class Ext.chart.series.Radar
74913  * @extends Ext.chart.series.Series
74914  *
74915  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
74916  * a constrained number of categories.
74917  *
74918  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
74919  * documentation for more information. A typical configuration object for the radar series could be:
74920  *
74921  *     @example
74922  *     var store = Ext.create('Ext.data.JsonStore', {
74923  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74924  *         data: [
74925  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74926  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74927  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74928  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74929  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74930  *         ]
74931  *     });
74932  *
74933  *     Ext.create('Ext.chart.Chart', {
74934  *         renderTo: Ext.getBody(),
74935  *         width: 500,
74936  *         height: 300,
74937  *         animate: true,
74938  *         theme:'Category2',
74939  *         store: store,
74940  *         axes: [{
74941  *             type: 'Radial',
74942  *             position: 'radial',
74943  *             label: {
74944  *                 display: true
74945  *             }
74946  *         }],
74947  *         series: [{
74948  *             type: 'radar',
74949  *             xField: 'name',
74950  *             yField: 'data3',
74951  *             showInLegend: true,
74952  *             showMarkers: true,
74953  *             markerConfig: {
74954  *                 radius: 5,
74955  *                 size: 5
74956  *             },
74957  *             style: {
74958  *                 'stroke-width': 2,
74959  *                 fill: 'none'
74960  *             }
74961  *         },{
74962  *             type: 'radar',
74963  *             xField: 'name',
74964  *             yField: 'data2',
74965  *             showMarkers: true,
74966  *             showInLegend: true,
74967  *             markerConfig: {
74968  *                 radius: 5,
74969  *                 size: 5
74970  *             },
74971  *             style: {
74972  *                 'stroke-width': 2,
74973  *                 fill: 'none'
74974  *             }
74975  *         },{
74976  *             type: 'radar',
74977  *             xField: 'name',
74978  *             yField: 'data5',
74979  *             showMarkers: true,
74980  *             showInLegend: true,
74981  *             markerConfig: {
74982  *                 radius: 5,
74983  *                 size: 5
74984  *             },
74985  *             style: {
74986  *                 'stroke-width': 2,
74987  *                 fill: 'none'
74988  *             }
74989  *         }]
74990  *     });
74991  *
74992  * In this configuration we add three series to the chart. Each of these series is bound to the same
74993  * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
74994  * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
74995  * for the markers of each series can be set by adding properties onto the markerConfig object.
74996  * Finally we override some theme styling properties by adding properties to the `style` object.
74997  *
74998  * @xtype radar
74999  */
75000 Ext.define('Ext.chart.series.Radar', {
75001
75002     /* Begin Definitions */
75003
75004     extend: 'Ext.chart.series.Series',
75005
75006     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
75007
75008     /* End Definitions */
75009
75010     type: "radar",
75011     alias: 'series.radar',
75012
75013
75014     rad: Math.PI / 180,
75015
75016     showInLegend: false,
75017
75018     /**
75019      * @cfg {Object} style
75020      * An object containing styles for overriding series styles from Theming.
75021      */
75022     style: {},
75023
75024     constructor: function(config) {
75025         this.callParent(arguments);
75026         var me = this,
75027             surface = me.chart.surface, i, l;
75028         me.group = surface.getGroup(me.seriesId);
75029         if (me.showMarkers) {
75030             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
75031         }
75032     },
75033
75034     /**
75035      * Draws the series for the current chart.
75036      */
75037     drawSeries: function() {
75038         var me = this,
75039             store = me.chart.getChartStore(),
75040             group = me.group,
75041             sprite,
75042             chart = me.chart,
75043             animate = chart.animate,
75044             field = me.field || me.yField,
75045             surface = chart.surface,
75046             chartBBox = chart.chartBBox,
75047             rendererAttributes,
75048             centerX, centerY,
75049             items,
75050             radius,
75051             maxValue = 0,
75052             fields = [],
75053             max = Math.max,
75054             cos = Math.cos,
75055             sin = Math.sin,
75056             pi2 = Math.PI * 2,
75057             l = store.getCount(),
75058             startPath, path, x, y, rho,
75059             i, nfields,
75060             seriesStyle = me.seriesStyle,
75061             seriesLabelStyle = me.seriesLabelStyle,
75062             first = chart.resizing || !me.radar,
75063             axis = chart.axes && chart.axes.get(0),
75064             aggregate = !(axis && axis.maximum);
75065
75066         me.setBBox();
75067
75068         maxValue = aggregate? 0 : (axis.maximum || 0);
75069
75070         Ext.apply(seriesStyle, me.style || {});
75071
75072         //if the store is empty then there's nothing to draw
75073         if (!store || !store.getCount()) {
75074             return;
75075         }
75076
75077         me.unHighlightItem();
75078         me.cleanHighlights();
75079
75080         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
75081         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
75082         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
75083         me.items = items = [];
75084
75085         if (aggregate) {
75086             //get all renderer fields
75087             chart.series.each(function(series) {
75088                 fields.push(series.yField);
75089             });
75090             //get maxValue to interpolate
75091             store.each(function(record, i) {
75092                 for (i = 0, nfields = fields.length; i < nfields; i++) {
75093                     maxValue = max(+record.get(fields[i]), maxValue);
75094                 }
75095             });
75096         }
75097         //ensure non-zero value.
75098         maxValue = maxValue || 1;
75099         //create path and items
75100         startPath = []; path = [];
75101         store.each(function(record, i) {
75102             rho = radius * record.get(field) / maxValue;
75103             x = rho * cos(i / l * pi2);
75104             y = rho * sin(i / l * pi2);
75105             if (i == 0) {
75106                 path.push('M', x + centerX, y + centerY);
75107                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
75108             } else {
75109                 path.push('L', x + centerX, y + centerY);
75110                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
75111             }
75112             items.push({
75113                 sprite: false, //TODO(nico): add markers
75114                 point: [centerX + x, centerY + y],
75115                 series: me
75116             });
75117         });
75118         path.push('Z');
75119         //create path sprite
75120         if (!me.radar) {
75121             me.radar = surface.add(Ext.apply({
75122                 type: 'path',
75123                 group: group,
75124                 path: startPath
75125             }, seriesStyle || {}));
75126         }
75127         //reset on resizing
75128         if (chart.resizing) {
75129             me.radar.setAttributes({
75130                 path: startPath
75131             }, true);
75132         }
75133         //render/animate
75134         if (chart.animate) {
75135             me.onAnimate(me.radar, {
75136                 to: Ext.apply({
75137                     path: path
75138                 }, seriesStyle || {})
75139             });
75140         } else {
75141             me.radar.setAttributes(Ext.apply({
75142                 path: path
75143             }, seriesStyle || {}), true);
75144         }
75145         //render markers, labels and callouts
75146         if (me.showMarkers) {
75147             me.drawMarkers();
75148         }
75149         me.renderLabels();
75150         me.renderCallouts();
75151     },
75152
75153     // @private draws the markers for the lines (if any).
75154     drawMarkers: function() {
75155         var me = this,
75156             chart = me.chart,
75157             surface = chart.surface,
75158             markerStyle = Ext.apply({}, me.markerStyle || {}),
75159             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
75160             items = me.items,
75161             type = endMarkerStyle.type,
75162             markerGroup = me.markerGroup,
75163             centerX = me.centerX,
75164             centerY = me.centerY,
75165             item, i, l, marker;
75166
75167         delete endMarkerStyle.type;
75168
75169         for (i = 0, l = items.length; i < l; i++) {
75170             item = items[i];
75171             marker = markerGroup.getAt(i);
75172             if (!marker) {
75173                 marker = Ext.chart.Shape[type](surface, Ext.apply({
75174                     group: markerGroup,
75175                     x: 0,
75176                     y: 0,
75177                     translate: {
75178                         x: centerX,
75179                         y: centerY
75180                     }
75181                 }, endMarkerStyle));
75182             }
75183             else {
75184                 marker.show();
75185             }
75186             if (chart.resizing) {
75187                 marker.setAttributes({
75188                     x: 0,
75189                     y: 0,
75190                     translate: {
75191                         x: centerX,
75192                         y: centerY
75193                     }
75194                 }, true);
75195             }
75196             marker._to = {
75197                 translate: {
75198                     x: item.point[0],
75199                     y: item.point[1]
75200                 }
75201             };
75202             //render/animate
75203             if (chart.animate) {
75204                 me.onAnimate(marker, {
75205                     to: marker._to
75206                 });
75207             }
75208             else {
75209                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
75210             }
75211         }
75212     },
75213
75214     isItemInPoint: function(x, y, item) {
75215         var point,
75216             tolerance = 10,
75217             abs = Math.abs;
75218         point = item.point;
75219         return (abs(point[0] - x) <= tolerance &&
75220                 abs(point[1] - y) <= tolerance);
75221     },
75222
75223     // @private callback for when creating a label sprite.
75224     onCreateLabel: function(storeItem, item, i, display) {
75225         var me = this,
75226             group = me.labelsGroup,
75227             config = me.label,
75228             centerX = me.centerX,
75229             centerY = me.centerY,
75230             point = item.point,
75231             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
75232
75233         return me.chart.surface.add(Ext.apply({
75234             'type': 'text',
75235             'text-anchor': 'middle',
75236             'group': group,
75237             'x': centerX,
75238             'y': centerY
75239         }, config || {}));
75240     },
75241
75242     // @private callback for when placing a label sprite.
75243     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75244         var me = this,
75245             chart = me.chart,
75246             resizing = chart.resizing,
75247             config = me.label,
75248             format = config.renderer,
75249             field = config.field,
75250             centerX = me.centerX,
75251             centerY = me.centerY,
75252             opt = {
75253                 x: item.point[0],
75254                 y: item.point[1]
75255             },
75256             x = opt.x - centerX,
75257             y = opt.y - centerY;
75258
75259         label.setAttributes({
75260             text: format(storeItem.get(field)),
75261             hidden: true
75262         },
75263         true);
75264
75265         if (resizing) {
75266             label.setAttributes({
75267                 x: centerX,
75268                 y: centerY
75269             }, true);
75270         }
75271
75272         if (animate) {
75273             label.show(true);
75274             me.onAnimate(label, {
75275                 to: opt
75276             });
75277         } else {
75278             label.setAttributes(opt, true);
75279             label.show(true);
75280         }
75281     },
75282
75283     // @private for toggling (show/hide) series.
75284     toggleAll: function(show) {
75285         var me = this,
75286             i, ln, shadow, shadows;
75287         if (!show) {
75288             Ext.chart.series.Radar.superclass.hideAll.call(me);
75289         }
75290         else {
75291             Ext.chart.series.Radar.superclass.showAll.call(me);
75292         }
75293         if (me.radar) {
75294             me.radar.setAttributes({
75295                 hidden: !show
75296             }, true);
75297             //hide shadows too
75298             if (me.radar.shadows) {
75299                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
75300                     shadow = shadows[i];
75301                     shadow.setAttributes({
75302                         hidden: !show
75303                     }, true);
75304                 }
75305             }
75306         }
75307     },
75308
75309     // @private hide all elements in the series.
75310     hideAll: function() {
75311         this.toggleAll(false);
75312         this.hideMarkers(0);
75313     },
75314
75315     // @private show all elements in the series.
75316     showAll: function() {
75317         this.toggleAll(true);
75318     },
75319
75320     // @private hide all markers that belong to `markerGroup`
75321     hideMarkers: function(index) {
75322         var me = this,
75323             count = me.markerGroup && me.markerGroup.getCount() || 0,
75324             i = index || 0;
75325         for (; i < count; i++) {
75326             me.markerGroup.getAt(i).hide(true);
75327         }
75328     }
75329 });
75330
75331
75332 /**
75333  * @class Ext.chart.series.Scatter
75334  * @extends Ext.chart.series.Cartesian
75335  *
75336  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
75337  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
75338  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
75339  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
75340  *
75341  *     @example
75342  *     var store = Ext.create('Ext.data.JsonStore', {
75343  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
75344  *         data: [
75345  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
75346  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
75347  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
75348  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
75349  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
75350  *         ]
75351  *     });
75352  *
75353  *     Ext.create('Ext.chart.Chart', {
75354  *         renderTo: Ext.getBody(),
75355  *         width: 500,
75356  *         height: 300,
75357  *         animate: true,
75358  *         theme:'Category2',
75359  *         store: store,
75360  *         axes: [{
75361  *             type: 'Numeric',
75362  *             position: 'left',
75363  *             fields: ['data2', 'data3'],
75364  *             title: 'Sample Values',
75365  *             grid: true,
75366  *             minimum: 0
75367  *         }, {
75368  *             type: 'Category',
75369  *             position: 'bottom',
75370  *             fields: ['name'],
75371  *             title: 'Sample Metrics'
75372  *         }],
75373  *         series: [{
75374  *             type: 'scatter',
75375  *             markerConfig: {
75376  *                 radius: 5,
75377  *                 size: 5
75378  *             },
75379  *             axis: 'left',
75380  *             xField: 'name',
75381  *             yField: 'data2'
75382  *         }, {
75383  *             type: 'scatter',
75384  *             markerConfig: {
75385  *                 radius: 5,
75386  *                 size: 5
75387  *             },
75388  *             axis: 'left',
75389  *             xField: 'name',
75390  *             yField: 'data3'
75391  *         }]
75392  *     });
75393  *
75394  * 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,
75395  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
75396  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
75397  * axis to show the current values of the elements.
75398  *
75399  * @xtype scatter
75400  */
75401 Ext.define('Ext.chart.series.Scatter', {
75402
75403     /* Begin Definitions */
75404
75405     extend: 'Ext.chart.series.Cartesian',
75406
75407     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
75408
75409     /* End Definitions */
75410
75411     type: 'scatter',
75412     alias: 'series.scatter',
75413
75414     /**
75415      * @cfg {Object} markerConfig
75416      * The display style for the scatter series markers.
75417      */
75418
75419     /**
75420      * @cfg {Object} style
75421      * Append styling properties to this object for it to override theme properties.
75422      */
75423     
75424     /**
75425      * @cfg {String/Array} axis
75426      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
75427      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
75428      * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
75429      */
75430
75431     constructor: function(config) {
75432         this.callParent(arguments);
75433         var me = this,
75434             shadow = me.chart.shadow,
75435             surface = me.chart.surface, i, l;
75436         Ext.apply(me, config, {
75437             style: {},
75438             markerConfig: {},
75439             shadowAttributes: [{
75440                 "stroke-width": 6,
75441                 "stroke-opacity": 0.05,
75442                 stroke: 'rgb(0, 0, 0)'
75443             }, {
75444                 "stroke-width": 4,
75445                 "stroke-opacity": 0.1,
75446                 stroke: 'rgb(0, 0, 0)'
75447             }, {
75448                 "stroke-width": 2,
75449                 "stroke-opacity": 0.15,
75450                 stroke: 'rgb(0, 0, 0)'
75451             }]
75452         });
75453         me.group = surface.getGroup(me.seriesId);
75454         if (shadow) {
75455             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
75456                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
75457             }
75458         }
75459     },
75460
75461     // @private Get chart and data boundaries
75462     getBounds: function() {
75463         var me = this,
75464             chart = me.chart,
75465             store = chart.getChartStore(),
75466             axes = [].concat(me.axis),
75467             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
75468
75469         me.setBBox();
75470         bbox = me.bbox;
75471
75472         for (i = 0, ln = axes.length; i < ln; i++) {
75473             axis = chart.axes.get(axes[i]);
75474             if (axis) {
75475                 ends = axis.calcEnds();
75476                 if (axis.position == 'top' || axis.position == 'bottom') {
75477                     minX = ends.from;
75478                     maxX = ends.to;
75479                 }
75480                 else {
75481                     minY = ends.from;
75482                     maxY = ends.to;
75483                 }
75484             }
75485         }
75486         // If a field was specified without a corresponding axis, create one to get bounds
75487         if (me.xField && !Ext.isNumber(minX)) {
75488             axis = Ext.create('Ext.chart.axis.Axis', {
75489                 chart: chart,
75490                 fields: [].concat(me.xField)
75491             }).calcEnds();
75492             minX = axis.from;
75493             maxX = axis.to;
75494         }
75495         if (me.yField && !Ext.isNumber(minY)) {
75496             axis = Ext.create('Ext.chart.axis.Axis', {
75497                 chart: chart,
75498                 fields: [].concat(me.yField)
75499             }).calcEnds();
75500             minY = axis.from;
75501             maxY = axis.to;
75502         }
75503
75504         if (isNaN(minX)) {
75505             minX = 0;
75506             maxX = store.getCount() - 1;
75507             xScale = bbox.width / (store.getCount() - 1);
75508         }
75509         else {
75510             xScale = bbox.width / (maxX - minX);
75511         }
75512
75513         if (isNaN(minY)) {
75514             minY = 0;
75515             maxY = store.getCount() - 1;
75516             yScale = bbox.height / (store.getCount() - 1);
75517         }
75518         else {
75519             yScale = bbox.height / (maxY - minY);
75520         }
75521
75522         return {
75523             bbox: bbox,
75524             minX: minX,
75525             minY: minY,
75526             xScale: xScale,
75527             yScale: yScale
75528         };
75529     },
75530
75531     // @private Build an array of paths for the chart
75532     getPaths: function() {
75533         var me = this,
75534             chart = me.chart,
75535             enableShadows = chart.shadow,
75536             store = chart.getChartStore(),
75537             group = me.group,
75538             bounds = me.bounds = me.getBounds(),
75539             bbox = me.bbox,
75540             xScale = bounds.xScale,
75541             yScale = bounds.yScale,
75542             minX = bounds.minX,
75543             minY = bounds.minY,
75544             boxX = bbox.x,
75545             boxY = bbox.y,
75546             boxHeight = bbox.height,
75547             items = me.items = [],
75548             attrs = [],
75549             x, y, xValue, yValue, sprite;
75550
75551         store.each(function(record, i) {
75552             xValue = record.get(me.xField);
75553             yValue = record.get(me.yField);
75554             //skip undefined values
75555             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
75556                 if (Ext.isDefined(Ext.global.console)) {
75557                     Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with an undefined value at ", record, xValue, yValue);
75558                 }
75559                 return;
75560             }
75561             // Ensure a value
75562             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
75563                 xValue = i;
75564             }
75565             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
75566                 yValue = i;
75567             }
75568             x = boxX + (xValue - minX) * xScale;
75569             y = boxY + boxHeight - (yValue - minY) * yScale;
75570             attrs.push({
75571                 x: x,
75572                 y: y
75573             });
75574
75575             me.items.push({
75576                 series: me,
75577                 value: [xValue, yValue],
75578                 point: [x, y],
75579                 storeItem: record
75580             });
75581
75582             // When resizing, reset before animating
75583             if (chart.animate && chart.resizing) {
75584                 sprite = group.getAt(i);
75585                 if (sprite) {
75586                     me.resetPoint(sprite);
75587                     if (enableShadows) {
75588                         me.resetShadow(sprite);
75589                     }
75590                 }
75591             }
75592         });
75593         return attrs;
75594     },
75595
75596     // @private translate point to the center
75597     resetPoint: function(sprite) {
75598         var bbox = this.bbox;
75599         sprite.setAttributes({
75600             translate: {
75601                 x: (bbox.x + bbox.width) / 2,
75602                 y: (bbox.y + bbox.height) / 2
75603             }
75604         }, true);
75605     },
75606
75607     // @private translate shadows of a sprite to the center
75608     resetShadow: function(sprite) {
75609         var me = this,
75610             shadows = sprite.shadows,
75611             shadowAttributes = me.shadowAttributes,
75612             ln = me.shadowGroups.length,
75613             bbox = me.bbox,
75614             i, attr;
75615         for (i = 0; i < ln; i++) {
75616             attr = Ext.apply({}, shadowAttributes[i]);
75617             if (attr.translate) {
75618                 attr.translate.x += (bbox.x + bbox.width) / 2;
75619                 attr.translate.y += (bbox.y + bbox.height) / 2;
75620             }
75621             else {
75622                 attr.translate = {
75623                     x: (bbox.x + bbox.width) / 2,
75624                     y: (bbox.y + bbox.height) / 2
75625                 };
75626             }
75627             shadows[i].setAttributes(attr, true);
75628         }
75629     },
75630
75631     // @private create a new point
75632     createPoint: function(attr, type) {
75633         var me = this,
75634             chart = me.chart,
75635             group = me.group,
75636             bbox = me.bbox;
75637
75638         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
75639             x: 0,
75640             y: 0,
75641             group: group,
75642             translate: {
75643                 x: (bbox.x + bbox.width) / 2,
75644                 y: (bbox.y + bbox.height) / 2
75645             }
75646         }, attr));
75647     },
75648
75649     // @private create a new set of shadows for a sprite
75650     createShadow: function(sprite, endMarkerStyle, type) {
75651         var me = this,
75652             chart = me.chart,
75653             shadowGroups = me.shadowGroups,
75654             shadowAttributes = me.shadowAttributes,
75655             lnsh = shadowGroups.length,
75656             bbox = me.bbox,
75657             i, shadow, shadows, attr;
75658
75659         sprite.shadows = shadows = [];
75660
75661         for (i = 0; i < lnsh; i++) {
75662             attr = Ext.apply({}, shadowAttributes[i]);
75663             if (attr.translate) {
75664                 attr.translate.x += (bbox.x + bbox.width) / 2;
75665                 attr.translate.y += (bbox.y + bbox.height) / 2;
75666             }
75667             else {
75668                 Ext.apply(attr, {
75669                     translate: {
75670                         x: (bbox.x + bbox.width) / 2,
75671                         y: (bbox.y + bbox.height) / 2
75672                     }
75673                 });
75674             }
75675             Ext.apply(attr, endMarkerStyle);
75676             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
75677                 x: 0,
75678                 y: 0,
75679                 group: shadowGroups[i]
75680             }, attr));
75681             shadows.push(shadow);
75682         }
75683     },
75684
75685     /**
75686      * Draws the series for the current chart.
75687      */
75688     drawSeries: function() {
75689         var me = this,
75690             chart = me.chart,
75691             store = chart.getChartStore(),
75692             group = me.group,
75693             enableShadows = chart.shadow,
75694             shadowGroups = me.shadowGroups,
75695             shadowAttributes = me.shadowAttributes,
75696             lnsh = shadowGroups.length,
75697             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
75698             rendererAttributes, shadowAttribute;
75699
75700         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
75701         type = endMarkerStyle.type;
75702         delete endMarkerStyle.type;
75703
75704         //if the store is empty then there's nothing to be rendered
75705         if (!store || !store.getCount()) {
75706             return;
75707         }
75708
75709         me.unHighlightItem();
75710         me.cleanHighlights();
75711
75712         attrs = me.getPaths();
75713         ln = attrs.length;
75714         for (i = 0; i < ln; i++) {
75715             attr = attrs[i];
75716             sprite = group.getAt(i);
75717             Ext.apply(attr, endMarkerStyle);
75718
75719             // Create a new sprite if needed (no height)
75720             if (!sprite) {
75721                 sprite = me.createPoint(attr, type);
75722                 if (enableShadows) {
75723                     me.createShadow(sprite, endMarkerStyle, type);
75724                 }
75725             }
75726
75727             shadows = sprite.shadows;
75728             if (chart.animate) {
75729                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75730                 sprite._to = rendererAttributes;
75731                 me.onAnimate(sprite, {
75732                     to: rendererAttributes
75733                 });
75734                 //animate shadows
75735                 for (shindex = 0; shindex < lnsh; shindex++) {
75736                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75737                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75738                         hidden: false,
75739                         translate: {
75740                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75741                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75742                         }
75743                     }, shadowAttribute), i, store);
75744                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
75745                 }
75746             }
75747             else {
75748                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75749                 sprite._to = rendererAttributes;
75750                 sprite.setAttributes(rendererAttributes, true);
75751                 //animate shadows
75752                 for (shindex = 0; shindex < lnsh; shindex++) {
75753                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75754                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75755                         hidden: false,
75756                         translate: {
75757                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75758                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75759                         } 
75760                     }, shadowAttribute), i, store);
75761                     shadows[shindex].setAttributes(rendererAttributes, true);
75762                 }
75763             }
75764             me.items[i].sprite = sprite;
75765         }
75766
75767         // Hide unused sprites
75768         ln = group.getCount();
75769         for (i = attrs.length; i < ln; i++) {
75770             group.getAt(i).hide(true);
75771         }
75772         me.renderLabels();
75773         me.renderCallouts();
75774     },
75775
75776     // @private callback for when creating a label sprite.
75777     onCreateLabel: function(storeItem, item, i, display) {
75778         var me = this,
75779             group = me.labelsGroup,
75780             config = me.label,
75781             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
75782             bbox = me.bbox;
75783
75784         return me.chart.surface.add(Ext.apply({
75785             type: 'text',
75786             group: group,
75787             x: item.point[0],
75788             y: bbox.y + bbox.height / 2
75789         }, endLabelStyle));
75790     },
75791
75792     // @private callback for when placing a label sprite.
75793     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75794         var me = this,
75795             chart = me.chart,
75796             resizing = chart.resizing,
75797             config = me.label,
75798             format = config.renderer,
75799             field = config.field,
75800             bbox = me.bbox,
75801             x = item.point[0],
75802             y = item.point[1],
75803             radius = item.sprite.attr.radius,
75804             bb, width, height, anim;
75805
75806         label.setAttributes({
75807             text: format(storeItem.get(field)),
75808             hidden: true
75809         }, true);
75810
75811         if (display == 'rotate') {
75812             label.setAttributes({
75813                 'text-anchor': 'start',
75814                 'rotation': {
75815                     x: x,
75816                     y: y,
75817                     degrees: -45
75818                 }
75819             }, true);
75820             //correct label position to fit into the box
75821             bb = label.getBBox();
75822             width = bb.width;
75823             height = bb.height;
75824             x = x < bbox.x? bbox.x : x;
75825             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
75826             y = (y - height < bbox.y)? bbox.y + height : y;
75827
75828         } else if (display == 'under' || display == 'over') {
75829             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
75830             bb = item.sprite.getBBox();
75831             bb.width = bb.width || (radius * 2);
75832             bb.height = bb.height || (radius * 2);
75833             y = y + (display == 'over'? -bb.height : bb.height);
75834             //correct label position to fit into the box
75835             bb = label.getBBox();
75836             width = bb.width/2;
75837             height = bb.height/2;
75838             x = x - width < bbox.x ? bbox.x + width : x;
75839             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
75840             y = y - height < bbox.y? bbox.y + height : y;
75841             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
75842         }
75843
75844         if (!chart.animate) {
75845             label.setAttributes({
75846                 x: x,
75847                 y: y
75848             }, true);
75849             label.show(true);
75850         }
75851         else {
75852             if (resizing) {
75853                 anim = item.sprite.getActiveAnimation();
75854                 if (anim) {
75855                     anim.on('afteranimate', function() {
75856                         label.setAttributes({
75857                             x: x,
75858                             y: y
75859                         }, true);
75860                         label.show(true);
75861                     });
75862                 }
75863                 else {
75864                     label.show(true);
75865                 }
75866             }
75867             else {
75868                 me.onAnimate(label, {
75869                     to: {
75870                         x: x,
75871                         y: y
75872                     }
75873                 });
75874             }
75875         }
75876     },
75877
75878     // @private callback for when placing a callout sprite.
75879     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
75880         var me = this,
75881             chart = me.chart,
75882             surface = chart.surface,
75883             resizing = chart.resizing,
75884             config = me.callouts,
75885             items = me.items,
75886             cur = item.point,
75887             normal,
75888             bbox = callout.label.getBBox(),
75889             offsetFromViz = 30,
75890             offsetToSide = 10,
75891             offsetBox = 3,
75892             boxx, boxy, boxw, boxh,
75893             p, clipRect = me.bbox,
75894             x, y;
75895
75896         //position
75897         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
75898         x = cur[0] + normal[0] * offsetFromViz;
75899         y = cur[1] + normal[1] * offsetFromViz;
75900
75901         //box position and dimensions
75902         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75903         boxy = y - bbox.height /2 - offsetBox;
75904         boxw = bbox.width + 2 * offsetBox;
75905         boxh = bbox.height + 2 * offsetBox;
75906
75907         //now check if we're out of bounds and invert the normal vector correspondingly
75908         //this may add new overlaps between labels (but labels won't be out of bounds).
75909         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
75910             normal[0] *= -1;
75911         }
75912         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
75913             normal[1] *= -1;
75914         }
75915
75916         //update positions
75917         x = cur[0] + normal[0] * offsetFromViz;
75918         y = cur[1] + normal[1] * offsetFromViz;
75919
75920         //update box position and dimensions
75921         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75922         boxy = y - bbox.height /2 - offsetBox;
75923         boxw = bbox.width + 2 * offsetBox;
75924         boxh = bbox.height + 2 * offsetBox;
75925
75926         if (chart.animate) {
75927             //set the line from the middle of the pie to the box.
75928             me.onAnimate(callout.lines, {
75929                 to: {
75930                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75931                 }
75932             }, true);
75933             //set box position
75934             me.onAnimate(callout.box, {
75935                 to: {
75936                     x: boxx,
75937                     y: boxy,
75938                     width: boxw,
75939                     height: boxh
75940                 }
75941             }, true);
75942             //set text position
75943             me.onAnimate(callout.label, {
75944                 to: {
75945                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75946                     y: y
75947                 }
75948             }, true);
75949         } else {
75950             //set the line from the middle of the pie to the box.
75951             callout.lines.setAttributes({
75952                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75953             }, true);
75954             //set box position
75955             callout.box.setAttributes({
75956                 x: boxx,
75957                 y: boxy,
75958                 width: boxw,
75959                 height: boxh
75960             }, true);
75961             //set text position
75962             callout.label.setAttributes({
75963                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75964                 y: y
75965             }, true);
75966         }
75967         for (p in callout) {
75968             callout[p].show(true);
75969         }
75970     },
75971
75972     // @private handles sprite animation for the series.
75973     onAnimate: function(sprite, attr) {
75974         sprite.show();
75975         return this.callParent(arguments);
75976     },
75977
75978     isItemInPoint: function(x, y, item) {
75979         var point,
75980             tolerance = 10,
75981             abs = Math.abs;
75982
75983         function dist(point) {
75984             var dx = abs(point[0] - x),
75985                 dy = abs(point[1] - y);
75986             return Math.sqrt(dx * dx + dy * dy);
75987         }
75988         point = item.point;
75989         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
75990             point[1] - tolerance <= y && point[1] + tolerance >= y);
75991     }
75992 });
75993
75994
75995 /**
75996  * @class Ext.chart.theme.Base
75997  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
75998  * @ignore
75999  */
76000 Ext.define('Ext.chart.theme.Base', {
76001
76002     /* Begin Definitions */
76003
76004     requires: ['Ext.chart.theme.Theme'],
76005
76006     /* End Definitions */
76007
76008     constructor: function(config) {
76009         Ext.chart.theme.call(this, config, {
76010             background: false,
76011             axis: {
76012                 stroke: '#444',
76013                 'stroke-width': 1
76014             },
76015             axisLabelTop: {
76016                 fill: '#444',
76017                 font: '12px Arial, Helvetica, sans-serif',
76018                 spacing: 2,
76019                 padding: 5,
76020                 renderer: function(v) { return v; }
76021             },
76022             axisLabelRight: {
76023                 fill: '#444',
76024                 font: '12px Arial, Helvetica, sans-serif',
76025                 spacing: 2,
76026                 padding: 5,
76027                 renderer: function(v) { return v; }
76028             },
76029             axisLabelBottom: {
76030                 fill: '#444',
76031                 font: '12px Arial, Helvetica, sans-serif',
76032                 spacing: 2,
76033                 padding: 5,
76034                 renderer: function(v) { return v; }
76035             },
76036             axisLabelLeft: {
76037                 fill: '#444',
76038                 font: '12px Arial, Helvetica, sans-serif',
76039                 spacing: 2,
76040                 padding: 5,
76041                 renderer: function(v) { return v; }
76042             },
76043             axisTitleTop: {
76044                 font: 'bold 18px Arial',
76045                 fill: '#444'
76046             },
76047             axisTitleRight: {
76048                 font: 'bold 18px Arial',
76049                 fill: '#444',
76050                 rotate: {
76051                     x:0, y:0,
76052                     degrees: 270
76053                 }
76054             },
76055             axisTitleBottom: {
76056                 font: 'bold 18px Arial',
76057                 fill: '#444'
76058             },
76059             axisTitleLeft: {
76060                 font: 'bold 18px Arial',
76061                 fill: '#444',
76062                 rotate: {
76063                     x:0, y:0,
76064                     degrees: 270
76065                 }
76066             },
76067             series: {
76068                 'stroke-width': 0
76069             },
76070             seriesLabel: {
76071                 font: '12px Arial',
76072                 fill: '#333'
76073             },
76074             marker: {
76075                 stroke: '#555',
76076                 fill: '#000',
76077                 radius: 3,
76078                 size: 3
76079             },
76080             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
76081             seriesThemes: [{
76082                 fill: "#115fa6"
76083             }, {
76084                 fill: "#94ae0a"
76085             }, {
76086                 fill: "#a61120"
76087             }, {
76088                 fill: "#ff8809"
76089             }, {
76090                 fill: "#ffd13e"
76091             }, {
76092                 fill: "#a61187"
76093             }, {
76094                 fill: "#24ad9a"
76095             }, {
76096                 fill: "#7c7474"
76097             }, {
76098                 fill: "#a66111"
76099             }],
76100             markerThemes: [{
76101                 fill: "#115fa6",
76102                 type: 'circle' 
76103             }, {
76104                 fill: "#94ae0a",
76105                 type: 'cross'
76106             }, {
76107                 fill: "#a61120",
76108                 type: 'plus'
76109             }]
76110         });
76111     }
76112 }, function() {
76113     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
76114         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
76115         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
76116         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
76117                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
76118                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
76119                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
76120                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
76121                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
76122         cats = categories.length;
76123     
76124     //Create themes from base colors
76125     for (; i < l; i++) {
76126         themes[names[i]] = (function(color) {
76127             return Ext.extend(themes.Base, {
76128                 constructor: function(config) {
76129                     themes.Base.prototype.constructor.call(this, Ext.apply({
76130                         baseColor: color
76131                     }, config));
76132                 }
76133             });
76134         })(palette[i]);
76135     }
76136     
76137     //Create theme from color array
76138     for (i = 0; i < cats; i++) {
76139         themes['Category' + (i + 1)] = (function(category) {
76140             return Ext.extend(themes.Base, {
76141                 constructor: function(config) {
76142                     themes.Base.prototype.constructor.call(this, Ext.apply({
76143                         colors: category
76144                     }, config));
76145                 }
76146             });
76147         })(categories[i]);
76148     }
76149 });
76150
76151 /**
76152  * @author Ed Spencer
76153  *
76154  * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
76155  * automatically configured with a {@link Ext.data.reader.Array}.
76156  *
76157  * A store configuration would be something like:
76158  *
76159  *     var store = Ext.create('Ext.data.ArrayStore', {
76160  *         // store configs
76161  *         autoDestroy: true,
76162  *         storeId: 'myStore',
76163  *         // reader configs
76164  *         idIndex: 0,
76165  *         fields: [
76166  *            'company',
76167  *            {name: 'price', type: 'float'},
76168  *            {name: 'change', type: 'float'},
76169  *            {name: 'pctChange', type: 'float'},
76170  *            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
76171  *         ]
76172  *     });
76173  *
76174  * This store is configured to consume a returned object of the form:
76175  *
76176  *     var myData = [
76177  *         ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
76178  *         ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
76179  *         ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
76180  *         ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
76181  *         ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
76182  *     ];
76183  *
76184  * An object literal of this form could also be used as the {@link #data} config option.
76185  *
76186  * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
76187  */
76188 Ext.define('Ext.data.ArrayStore', {
76189     extend: 'Ext.data.Store',
76190     alias: 'store.array',
76191     uses: ['Ext.data.reader.Array'],
76192
76193     constructor: function(config) {
76194         config = config || {};
76195
76196         Ext.applyIf(config, {
76197             proxy: {
76198                 type: 'memory',
76199                 reader: 'array'
76200             }
76201         });
76202
76203         this.callParent([config]);
76204     },
76205
76206     loadData: function(data, append) {
76207         if (this.expandData === true) {
76208             var r = [],
76209                 i = 0,
76210                 ln = data.length;
76211
76212             for (; i < ln; i++) {
76213                 r[r.length] = [data[i]];
76214             }
76215
76216             data = r;
76217         }
76218
76219         this.callParent([data, append]);
76220     }
76221 }, function() {
76222     // backwards compat
76223     Ext.data.SimpleStore = Ext.data.ArrayStore;
76224     // Ext.reg('simplestore', Ext.data.SimpleStore);
76225 });
76226
76227 /**
76228  * @author Ed Spencer
76229  * @class Ext.data.Batch
76230  *
76231  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
76232  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
76233  * event if any of the Operations encounter an exception.</p>
76234  *
76235  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
76236  *
76237  */
76238 Ext.define('Ext.data.Batch', {
76239     mixins: {
76240         observable: 'Ext.util.Observable'
76241     },
76242
76243     /**
76244      * @property {Boolean} autoStart
76245      * True to immediately start processing the batch as soon as it is constructed.
76246      */
76247     autoStart: false,
76248
76249     /**
76250      * @property {Number} current
76251      * The index of the current operation being executed
76252      */
76253     current: -1,
76254
76255     /**
76256      * @property {Number} total
76257      * The total number of operations in this batch. Read only
76258      */
76259     total: 0,
76260
76261     /**
76262      * @property {Boolean} isRunning
76263      * True if the batch is currently running
76264      */
76265     isRunning: false,
76266
76267     /**
76268      * @property {Boolean} isComplete
76269      * True if this batch has been executed completely
76270      */
76271     isComplete: false,
76272
76273     /**
76274      * @property {Boolean} hasException
76275      * True if this batch has encountered an exception. This is cleared at the start of each operation
76276      */
76277     hasException: false,
76278
76279     /**
76280      * @property {Boolean} pauseOnException
76281      * True to automatically pause the execution of the batch if any operation encounters an exception
76282      */
76283     pauseOnException: true,
76284
76285     /**
76286      * Creates new Batch object.
76287      * @param {Object} [config] Config object
76288      */
76289     constructor: function(config) {
76290         var me = this;
76291
76292         me.addEvents(
76293           /**
76294            * @event complete
76295            * Fired when all operations of this batch have been completed
76296            * @param {Ext.data.Batch} batch The batch object
76297            * @param {Object} operation The last operation that was executed
76298            */
76299           'complete',
76300
76301           /**
76302            * @event exception
76303            * Fired when a operation encountered an exception
76304            * @param {Ext.data.Batch} batch The batch object
76305            * @param {Object} operation The operation that encountered the exception
76306            */
76307           'exception',
76308
76309           /**
76310            * @event operationcomplete
76311            * Fired when each operation of the batch completes
76312            * @param {Ext.data.Batch} batch The batch object
76313            * @param {Object} operation The operation that just completed
76314            */
76315           'operationcomplete'
76316         );
76317
76318         me.mixins.observable.constructor.call(me, config);
76319
76320         /**
76321          * Ordered array of operations that will be executed by this batch
76322          * @property {Ext.data.Operation[]} operations
76323          */
76324         me.operations = [];
76325     },
76326
76327     /**
76328      * Adds a new operation to this batch
76329      * @param {Object} operation The {@link Ext.data.Operation Operation} object
76330      */
76331     add: function(operation) {
76332         this.total++;
76333
76334         operation.setBatch(this);
76335
76336         this.operations.push(operation);
76337     },
76338
76339     /**
76340      * Kicks off the execution of the batch, continuing from the next operation if the previous
76341      * operation encountered an exception, or if execution was paused
76342      */
76343     start: function() {
76344         this.hasException = false;
76345         this.isRunning = true;
76346
76347         this.runNextOperation();
76348     },
76349
76350     /**
76351      * @private
76352      * Runs the next operation, relative to this.current.
76353      */
76354     runNextOperation: function() {
76355         this.runOperation(this.current + 1);
76356     },
76357
76358     /**
76359      * Pauses execution of the batch, but does not cancel the current operation
76360      */
76361     pause: function() {
76362         this.isRunning = false;
76363     },
76364
76365     /**
76366      * Executes a operation by its numeric index
76367      * @param {Number} index The operation index to run
76368      */
76369     runOperation: function(index) {
76370         var me = this,
76371             operations = me.operations,
76372             operation  = operations[index],
76373             onProxyReturn;
76374
76375         if (operation === undefined) {
76376             me.isRunning  = false;
76377             me.isComplete = true;
76378             me.fireEvent('complete', me, operations[operations.length - 1]);
76379         } else {
76380             me.current = index;
76381
76382             onProxyReturn = function(operation) {
76383                 var hasException = operation.hasException();
76384
76385                 if (hasException) {
76386                     me.hasException = true;
76387                     me.fireEvent('exception', me, operation);
76388                 } else {
76389                     me.fireEvent('operationcomplete', me, operation);
76390                 }
76391
76392                 if (hasException && me.pauseOnException) {
76393                     me.pause();
76394                 } else {
76395                     operation.setCompleted();
76396                     me.runNextOperation();
76397                 }
76398             };
76399
76400             operation.setStarted();
76401
76402             me.proxy[operation.action](operation, onProxyReturn, me);
76403         }
76404     }
76405 });
76406 /**
76407  * @author Ed Spencer
76408  * @class Ext.data.BelongsToAssociation
76409  * @extends Ext.data.Association
76410  *
76411  * Represents a many to one association with another model. The owner model is expected to have
76412  * a foreign key which references the primary key of the associated model:
76413  *
76414  *     Ext.define('Category', {
76415  *         extend: 'Ext.data.Model',
76416  *         fields: [
76417  *             { name: 'id',   type: 'int' },
76418  *             { name: 'name', type: 'string' }
76419  *         ]
76420  *     });
76421  *
76422  *     Ext.define('Product', {
76423  *         extend: 'Ext.data.Model',
76424  *         fields: [
76425  *             { name: 'id',          type: 'int' },
76426  *             { name: 'category_id', type: 'int' },
76427  *             { name: 'name',        type: 'string' }
76428  *         ],
76429  *         // we can use the belongsTo shortcut on the model to create a belongsTo association
76430  *         associations: [
76431  *             { type: 'belongsTo', model: 'Category' }
76432  *         ]
76433  *     });
76434  *
76435  * In the example above we have created models for Products and Categories, and linked them together
76436  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
76437  * based on the Product's category_id, and provides new functions on the Product model:
76438  *
76439  * ## Generated getter function
76440  *
76441  * The first function that is added to the owner model is a getter function:
76442  *
76443  *     var product = new Product({
76444  *         id: 100,
76445  *         category_id: 20,
76446  *         name: 'Sneakers'
76447  *     });
76448  *
76449  *     product.getCategory(function(category, operation) {
76450  *         // do something with the category object
76451  *         alert(category.get('id')); // alerts 20
76452  *     }, this);
76453  *
76454  * The getCategory function was created on the Product model when we defined the association. This uses the
76455  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
76456  * callback when it has loaded.
76457  *
76458  * The new getCategory function will also accept an object containing success, failure and callback properties
76459  * - callback will always be called, success will only be called if the associated model was loaded successfully
76460  * and failure will only be called if the associatied model could not be loaded:
76461  *
76462  *     product.getCategory({
76463  *         callback: function(category, operation) {}, // a function that will always be called
76464  *         success : function(category, operation) {}, // a function that will only be called if the load succeeded
76465  *         failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
76466  *         scope   : this // optionally pass in a scope object to execute the callbacks in
76467  *     });
76468  *
76469  * In each case above the callbacks are called with two arguments - the associated model instance and the
76470  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
76471  * useful when the instance could not be loaded.
76472  *
76473  * ## Generated setter function
76474  *
76475  * The second generated function sets the associated model instance - if only a single argument is passed to
76476  * the setter then the following two calls are identical:
76477  *
76478  *     // this call...
76479  *     product.setCategory(10);
76480  *
76481  *     // is equivalent to this call:
76482  *     product.set('category_id', 10);
76483  *
76484  * If we pass in a second argument, the model will be automatically saved and the second argument passed to
76485  * the owner model's {@link Ext.data.Model#save save} method:
76486  *
76487  *     product.setCategory(10, function(product, operation) {
76488  *         // the product has been saved
76489  *         alert(product.get('category_id')); //now alerts 10
76490  *     });
76491  *
76492  *     //alternative syntax:
76493  *     product.setCategory(10, {
76494  *         callback: function(product, operation), // a function that will always be called
76495  *         success : function(product, operation), // a function that will only be called if the load succeeded
76496  *         failure : function(product, operation), // a function that will only be called if the load did not succeed
76497  *         scope   : this //optionally pass in a scope object to execute the callbacks in
76498  *     })
76499  *
76500  * ## Customisation
76501  *
76502  * Associations reflect on the models they are linking to automatically set up properties such as the
76503  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
76504  *
76505  *     Ext.define('Product', {
76506  *         fields: [...],
76507  *
76508  *         associations: [
76509  *             { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
76510  *         ]
76511  *     });
76512  *
76513  * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
76514  * with our own settings. Usually this will not be needed.
76515  */
76516 Ext.define('Ext.data.BelongsToAssociation', {
76517     extend: 'Ext.data.Association',
76518
76519     alias: 'association.belongsto',
76520
76521     /**
76522      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
76523      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
76524      * model called Product would set up a product_id foreign key.
76525      *
76526      *     Ext.define('Order', {
76527      *         extend: 'Ext.data.Model',
76528      *         fields: ['id', 'date'],
76529      *         hasMany: 'Product'
76530      *     });
76531      *
76532      *     Ext.define('Product', {
76533      *         extend: 'Ext.data.Model',
76534      *         fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
76535      *         belongsTo: 'Group'
76536      *     });
76537      *     var product = new Product({
76538      *         id: 1,
76539      *         name: 'Product 1',
76540      *         order_id: 22
76541      *     }, 1);
76542      *     product.getOrder(); // Will make a call to the server asking for order_id 22
76543      *
76544      */
76545
76546     /**
76547      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
76548      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
76549      */
76550
76551     /**
76552      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
76553      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
76554      */
76555
76556     /**
76557      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
76558      * Use 'belongsTo' to create a HasManyAssocation
76559      *
76560      *     associations: [{
76561      *         type: 'belongsTo',
76562      *         model: 'User'
76563      *     }]
76564      */
76565     constructor: function(config) {
76566         this.callParent(arguments);
76567
76568         var me             = this,
76569             ownerProto     = me.ownerModel.prototype,
76570             associatedName = me.associatedName,
76571             getterName     = me.getterName || 'get' + associatedName,
76572             setterName     = me.setterName || 'set' + associatedName;
76573
76574         Ext.applyIf(me, {
76575             name        : associatedName,
76576             foreignKey  : associatedName.toLowerCase() + "_id",
76577             instanceName: associatedName + 'BelongsToInstance',
76578             associationKey: associatedName.toLowerCase()
76579         });
76580
76581         ownerProto[getterName] = me.createGetter();
76582         ownerProto[setterName] = me.createSetter();
76583     },
76584
76585     /**
76586      * @private
76587      * Returns a setter function to be placed on the owner model's prototype
76588      * @return {Function} The setter function
76589      */
76590     createSetter: function() {
76591         var me              = this,
76592             ownerModel      = me.ownerModel,
76593             associatedModel = me.associatedModel,
76594             foreignKey      = me.foreignKey,
76595             primaryKey      = me.primaryKey;
76596
76597         //'this' refers to the Model instance inside this function
76598         return function(value, options, scope) {
76599             this.set(foreignKey, value);
76600
76601             if (typeof options == 'function') {
76602                 options = {
76603                     callback: options,
76604                     scope: scope || this
76605                 };
76606             }
76607
76608             if (Ext.isObject(options)) {
76609                 return this.save(options);
76610             }
76611         };
76612     },
76613
76614     /**
76615      * @private
76616      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
76617      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
76618      * @return {Function} The getter function
76619      */
76620     createGetter: function() {
76621         var me              = this,
76622             ownerModel      = me.ownerModel,
76623             associatedName  = me.associatedName,
76624             associatedModel = me.associatedModel,
76625             foreignKey      = me.foreignKey,
76626             primaryKey      = me.primaryKey,
76627             instanceName    = me.instanceName;
76628
76629         //'this' refers to the Model instance inside this function
76630         return function(options, scope) {
76631             options = options || {};
76632
76633             var model = this,
76634                 foreignKeyId = model.get(foreignKey),
76635                 instance,
76636                 args;
76637
76638             if (model[instanceName] === undefined) {
76639                 instance = Ext.ModelManager.create({}, associatedName);
76640                 instance.set(primaryKey, foreignKeyId);
76641
76642                 if (typeof options == 'function') {
76643                     options = {
76644                         callback: options,
76645                         scope: scope || model
76646                     };
76647                 }
76648
76649                 associatedModel.load(foreignKeyId, options);
76650                 model[instanceName] = associatedModel;
76651                 return associatedModel;
76652             } else {
76653                 instance = model[instanceName];
76654                 args = [instance];
76655                 scope = scope || model;
76656
76657                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
76658                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
76659                 //instead of the association layer.
76660                 Ext.callback(options, scope, args);
76661                 Ext.callback(options.success, scope, args);
76662                 Ext.callback(options.failure, scope, args);
76663                 Ext.callback(options.callback, scope, args);
76664
76665                 return instance;
76666             }
76667         };
76668     },
76669
76670     /**
76671      * Read associated data
76672      * @private
76673      * @param {Ext.data.Model} record The record we're writing to
76674      * @param {Ext.data.reader.Reader} reader The reader for the associated model
76675      * @param {Object} associationData The raw associated data
76676      */
76677     read: function(record, reader, associationData){
76678         record[this.instanceName] = reader.read([associationData]).records[0];
76679     }
76680 });
76681
76682 /**
76683  * @class Ext.data.BufferStore
76684  * @extends Ext.data.Store
76685  * @ignore
76686  */
76687 Ext.define('Ext.data.BufferStore', {
76688     extend: 'Ext.data.Store',
76689     alias: 'store.buffer',
76690     sortOnLoad: false,
76691     filterOnLoad: false,
76692     
76693     constructor: function() {
76694         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
76695     }
76696 });
76697 /**
76698  * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
76699  * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
76700  * error conditions, etc).
76701  *
76702  * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
76703  * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
76704  *
76705  * # Specification
76706  *
76707  * For additional information consult the [Ext.Direct Specification][1].
76708  *
76709  * # Providers
76710  *
76711  * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
76712  * server. There are several providers that exist in the core at the moment:
76713  *
76714  * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
76715  * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
76716  * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
76717  *
76718  * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
76719  *
76720  * # Router
76721  *
76722  * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
76723  * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
76724  * solution and replace it with one that uses C# without changing the client side JavaScript at all.
76725  *
76726  * # Server side events
76727  *
76728  * Custom events from the server may be handled by the client by adding listeners, for example:
76729  *
76730  *     {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
76731  *
76732  *     // add a handler for a 'message' event sent by the server
76733  *     Ext.direct.Manager.on('message', function(e){
76734  *         out.append(String.format('<p><i>{0}</i></p>', e.data));
76735  *         out.el.scrollTo('t', 100000, true);
76736  *     });
76737  *
76738  *    [1]: http://sencha.com/products/extjs/extdirect
76739  *
76740  * @singleton
76741  * @alternateClassName Ext.Direct
76742  */
76743 Ext.define('Ext.direct.Manager', {
76744
76745     /* Begin Definitions */
76746     singleton: true,
76747
76748     mixins: {
76749         observable: 'Ext.util.Observable'
76750     },
76751
76752     requires: ['Ext.util.MixedCollection'],
76753
76754     statics: {
76755         exceptions: {
76756             TRANSPORT: 'xhr',
76757             PARSE: 'parse',
76758             LOGIN: 'login',
76759             SERVER: 'exception'
76760         }
76761     },
76762
76763     /* End Definitions */
76764
76765     constructor: function(){
76766         var me = this;
76767
76768         me.addEvents(
76769             /**
76770              * @event event
76771              * Fires after an event.
76772              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
76773              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76774              */
76775             'event',
76776             /**
76777              * @event exception
76778              * Fires after an event exception.
76779              * @param {Ext.direct.Event} e The event type that occurred.
76780              */
76781             'exception'
76782         );
76783         me.transactions = Ext.create('Ext.util.MixedCollection');
76784         me.providers = Ext.create('Ext.util.MixedCollection');
76785
76786         me.mixins.observable.constructor.call(me);
76787     },
76788
76789     /**
76790      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
76791      * is not already connected, it will auto-connect.
76792      *
76793      *     var pollProv = new Ext.direct.PollingProvider({
76794      *         url: 'php/poll2.php'
76795      *     });
76796      *
76797      *     Ext.direct.Manager.addProvider({
76798      *         "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
76799      *         "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
76800      *         "actions":{              // each property within the actions object represents a Class
76801      *             "TestAction":[       // array of methods within each server side Class
76802      *             {
76803      *                 "name":"doEcho", // name of method
76804      *                 "len":1
76805      *             },{
76806      *                 "name":"multiply",
76807      *                 "len":1
76808      *             },{
76809      *                 "name":"doForm",
76810      *                 "formHandler":true, // handle form on server with Ext.Direct.Transaction
76811      *                 "len":1
76812      *             }]
76813      *         },
76814      *         "namespace":"myApplication",// namespace to create the Remoting Provider in
76815      *     },{
76816      *         type: 'polling', // create a {@link Ext.direct.PollingProvider}
76817      *         url:  'php/poll.php'
76818      *     }, pollProv); // reference to previously created instance
76819      *
76820      * @param {Ext.direct.Provider/Object...} provider
76821      * Accepts any number of Provider descriptions (an instance or config object for
76822      * a Provider). Each Provider description instructs Ext.Directhow to create
76823      * client-side stub methods.
76824      */
76825     addProvider : function(provider){
76826         var me = this,
76827             args = arguments,
76828             i = 0,
76829             len;
76830
76831         if (args.length > 1) {
76832             for (len = args.length; i < len; ++i) {
76833                 me.addProvider(args[i]);
76834             }
76835             return;
76836         }
76837
76838         // if provider has not already been instantiated
76839         if (!provider.isProvider) {
76840             provider = Ext.create('direct.' + provider.type + 'provider', provider);
76841         }
76842         me.providers.add(provider);
76843         provider.on('data', me.onProviderData, me);
76844
76845
76846         if (!provider.isConnected()) {
76847             provider.connect();
76848         }
76849
76850         return provider;
76851     },
76852
76853     /**
76854      * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
76855      * provider is {@link #addProvider added}.
76856      * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
76857      */
76858     getProvider : function(id){
76859         return id.isProvider ? id : this.providers.get(id);
76860     },
76861
76862     /**
76863      * Removes the provider.
76864      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
76865      * @return {Ext.direct.Provider} The provider, null if not found.
76866      */
76867     removeProvider : function(provider){
76868         var me = this,
76869             providers = me.providers;
76870
76871         provider = provider.isProvider ? provider : providers.get(provider);
76872
76873         if (provider) {
76874             provider.un('data', me.onProviderData, me);
76875             providers.remove(provider);
76876             return provider;
76877         }
76878         return null;
76879     },
76880
76881     /**
76882      * Adds a transaction to the manager.
76883      * @private
76884      * @param {Ext.direct.Transaction} transaction The transaction to add
76885      * @return {Ext.direct.Transaction} transaction
76886      */
76887     addTransaction: function(transaction){
76888         this.transactions.add(transaction);
76889         return transaction;
76890     },
76891
76892     /**
76893      * Removes a transaction from the manager.
76894      * @private
76895      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
76896      * @return {Ext.direct.Transaction} transaction
76897      */
76898     removeTransaction: function(transaction){
76899         transaction = this.getTransaction(transaction);
76900         this.transactions.remove(transaction);
76901         return transaction;
76902     },
76903
76904     /**
76905      * Gets a transaction
76906      * @private
76907      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
76908      * @return {Ext.direct.Transaction}
76909      */
76910     getTransaction: function(transaction){
76911         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
76912     },
76913
76914     onProviderData : function(provider, event){
76915         var me = this,
76916             i = 0,
76917             len;
76918
76919         if (Ext.isArray(event)) {
76920             for (len = event.length; i < len; ++i) {
76921                 me.onProviderData(provider, event[i]);
76922             }
76923             return;
76924         }
76925         if (event.name && event.name != 'event' && event.name != 'exception') {
76926             me.fireEvent(event.name, event);
76927         } else if (event.status === false) {
76928             me.fireEvent('exception', event);
76929         }
76930         me.fireEvent('event', event, provider);
76931     }
76932 }, function(){
76933     // Backwards compatibility
76934     Ext.Direct = Ext.direct.Manager;
76935 });
76936
76937 /**
76938  * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
76939  * request is made, the transport mechanism is handed off to the appropriate
76940  * {@link Ext.direct.RemotingProvider Provider} to complete the call.
76941  *
76942  * # Specifying the function
76943  *
76944  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
76945  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
76946  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
76947  * allows you to specify a different remoting method for each CRUD action.
76948  *
76949  * # Parameters
76950  *
76951  * This proxy provides options to help configure which parameters will be sent to the server.
76952  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
76953  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
76954  * the remoting method parameters are passed.
76955  *
76956  * # Example Usage
76957  *
76958  *     Ext.define('User', {
76959  *         extend: 'Ext.data.Model',
76960  *         fields: ['firstName', 'lastName'],
76961  *         proxy: {
76962  *             type: 'direct',
76963  *             directFn: MyApp.getUsers,
76964  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
76965  *         }
76966  *     });
76967  *     User.load(1);
76968  */
76969 Ext.define('Ext.data.proxy.Direct', {
76970     /* Begin Definitions */
76971
76972     extend: 'Ext.data.proxy.Server',
76973     alternateClassName: 'Ext.data.DirectProxy',
76974
76975     alias: 'proxy.direct',
76976
76977     requires: ['Ext.direct.Manager'],
76978
76979     /* End Definitions */
76980
76981     /**
76982      * @cfg {String/String[]} paramOrder
76983      * Defaults to undefined. A list of params to be executed server side.  Specify the params in the order in
76984      * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
76985      * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
76986      * acceptable:
76987      *
76988      *     paramOrder: ['param1','param2','param3']
76989      *     paramOrder: 'param1 param2 param3'
76990      *     paramOrder: 'param1,param2,param3'
76991      *     paramOrder: 'param1|param2|param'
76992      */
76993     paramOrder: undefined,
76994
76995     /**
76996      * @cfg {Boolean} paramsAsHash
76997      * Send parameters as a collection of named arguments.
76998      * Providing a {@link #paramOrder} nullifies this configuration.
76999      */
77000     paramsAsHash: true,
77001
77002     /**
77003      * @cfg {Function} directFn
77004      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
77005      * for Store's which will not implement a full CRUD api.
77006      */
77007     directFn : undefined,
77008
77009     /**
77010      * @cfg {Object} api
77011      * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
77012      * function call.
77013      */
77014
77015     /**
77016      * @cfg {Object} extraParams
77017      * Extra parameters that will be included on every read request. Individual requests with params
77018      * of the same name will override these params when they are in conflict.
77019      */
77020
77021     // private
77022     paramOrderRe: /[\s,|]/,
77023
77024     constructor: function(config){
77025         var me = this;
77026
77027         Ext.apply(me, config);
77028         if (Ext.isString(me.paramOrder)) {
77029             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
77030         }
77031         me.callParent(arguments);
77032     },
77033
77034     doRequest: function(operation, callback, scope) {
77035         var me = this,
77036             writer = me.getWriter(),
77037             request = me.buildRequest(operation, callback, scope),
77038             fn = me.api[request.action]  || me.directFn,
77039             args = [],
77040             params = request.params,
77041             paramOrder = me.paramOrder,
77042             method,
77043             i = 0,
77044             len;
77045
77046         if (!fn) {
77047             Ext.Error.raise('No direct function specified for this proxy');
77048         }
77049
77050         if (operation.allowWrite()) {
77051             request = writer.write(request);
77052         }
77053
77054         if (operation.action == 'read') {
77055             // We need to pass params
77056             method = fn.directCfg.method;
77057
77058             if (method.ordered) {
77059                 if (method.len > 0) {
77060                     if (paramOrder) {
77061                         for (len = paramOrder.length; i < len; ++i) {
77062                             args.push(params[paramOrder[i]]);
77063                         }
77064                     } else if (me.paramsAsHash) {
77065                         args.push(params);
77066                     }
77067                 }
77068             } else {
77069                 args.push(params);
77070             }
77071         } else {
77072             args.push(request.jsonData);
77073         }
77074
77075         Ext.apply(request, {
77076             args: args,
77077             directFn: fn
77078         });
77079         args.push(me.createRequestCallback(request, operation, callback, scope), me);
77080         fn.apply(window, args);
77081     },
77082
77083     /*
77084      * Inherit docs. We don't apply any encoding here because
77085      * all of the direct requests go out as jsonData
77086      */
77087     applyEncoding: function(value){
77088         return value;
77089     },
77090
77091     createRequestCallback: function(request, operation, callback, scope){
77092         var me = this;
77093
77094         return function(data, event){
77095             me.processResponse(event.status, operation, request, event, callback, scope);
77096         };
77097     },
77098
77099     // inherit docs
77100     extractResponseData: function(response){
77101         return Ext.isDefined(response.result) ? response.result : response.data;
77102     },
77103
77104     // inherit docs
77105     setException: function(operation, response) {
77106         operation.setException(response.message);
77107     },
77108
77109     // inherit docs
77110     buildUrl: function(){
77111         return '';
77112     }
77113 });
77114
77115 /**
77116  * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
77117  * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
77118  * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
77119  * {@link Ext.data.Store} configured as needed.
77120  *
77121  * **Note:** Although they are not listed, this class inherits all of the config options of:
77122  *
77123  * - **{@link Ext.data.Store Store}**
77124  *
77125  * - **{@link Ext.data.reader.Json JsonReader}**
77126  *
77127  *   - **{@link Ext.data.reader.Json#root root}**
77128  *   - **{@link Ext.data.reader.Json#idProperty idProperty}**
77129  *   - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
77130  *
77131  * - **{@link Ext.data.proxy.Direct DirectProxy}**
77132  *
77133  *   - **{@link Ext.data.proxy.Direct#directFn directFn}**
77134  *   - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
77135  *   - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
77136  *
77137  */
77138 Ext.define('Ext.data.DirectStore', {
77139     /* Begin Definitions */
77140     
77141     extend: 'Ext.data.Store',
77142     
77143     alias: 'store.direct',
77144     
77145     requires: ['Ext.data.proxy.Direct'],
77146    
77147     /* End Definitions */
77148
77149     constructor : function(config){
77150         config = Ext.apply({}, config);
77151         if (!config.proxy) {
77152             var proxy = {
77153                 type: 'direct',
77154                 reader: {
77155                     type: 'json'
77156                 }
77157             };
77158             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
77159             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
77160             config.proxy = proxy;
77161         }
77162         this.callParent([config]);
77163     }    
77164 });
77165
77166 /**
77167  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
77168  * {@link #ordinalize ordinalizes} words. Sample usage:
77169  *
77170  *     //turning singular words into plurals
77171  *     Ext.util.Inflector.pluralize('word'); //'words'
77172  *     Ext.util.Inflector.pluralize('person'); //'people'
77173  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
77174  *
77175  *     //turning plurals into singulars
77176  *     Ext.util.Inflector.singularize('words'); //'word'
77177  *     Ext.util.Inflector.singularize('people'); //'person'
77178  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
77179  *
77180  *     //ordinalizing numbers
77181  *     Ext.util.Inflector.ordinalize(11); //"11th"
77182  *     Ext.util.Inflector.ordinalize(21); //"21th"
77183  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
77184  *
77185  * # Customization
77186  *
77187  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
77188  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
77189  * Here is how we might add a rule that pluralizes "ox" to "oxen":
77190  *
77191  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
77192  *
77193  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
77194  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
77195  * how we could add the inverse rule:
77196  *
77197  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
77198  *
77199  * Note that the ox/oxen rules are present by default.
77200  */
77201 Ext.define('Ext.util.Inflector', {
77202
77203     /* Begin Definitions */
77204
77205     singleton: true,
77206
77207     /* End Definitions */
77208
77209     /**
77210      * @private
77211      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
77212      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
77213      * part of the regular expression. This is managed by the {@link #plural} method.
77214      * @property {Array} plurals
77215      */
77216     plurals: [
77217         [(/(quiz)$/i),                "$1zes"  ],
77218         [(/^(ox)$/i),                 "$1en"   ],
77219         [(/([m|l])ouse$/i),           "$1ice"  ],
77220         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
77221         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
77222         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
77223         [(/(hive)$/i),                "$1s"    ],
77224         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
77225         [(/sis$/i),                   "ses"    ],
77226         [(/([ti])um$/i),              "$1a"    ],
77227         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
77228         [(/(bu)s$/i),                 "$1ses"  ],
77229         [(/(alias|status|sex)$/i),    "$1es"   ],
77230         [(/(octop|vir)us$/i),         "$1i"    ],
77231         [(/(ax|test)is$/i),           "$1es"   ],
77232         [(/^person$/),                "people" ],
77233         [(/^man$/),                   "men"    ],
77234         [(/^(child)$/),               "$1ren"  ],
77235         [(/s$/i),                     "s"      ],
77236         [(/$/),                       "s"      ]
77237     ],
77238
77239     /**
77240      * @private
77241      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
77242      * regular expression that matches the plural form of a word, the second must be a String that replaces the
77243      * matched part of the regular expression. This is managed by the {@link #singular} method.
77244      * @property {Array} singulars
77245      */
77246     singulars: [
77247       [(/(quiz)zes$/i),                                                    "$1"     ],
77248       [(/(matr)ices$/i),                                                   "$1ix"   ],
77249       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
77250       [(/^(ox)en/i),                                                       "$1"     ],
77251       [(/(alias|status)es$/i),                                             "$1"     ],
77252       [(/(octop|vir)i$/i),                                                 "$1us"   ],
77253       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
77254       [(/(shoe)s$/i),                                                      "$1"     ],
77255       [(/(o)es$/i),                                                        "$1"     ],
77256       [(/(bus)es$/i),                                                      "$1"     ],
77257       [(/([m|l])ice$/i),                                                   "$1ouse" ],
77258       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
77259       [(/(m)ovies$/i),                                                     "$1ovie" ],
77260       [(/(s)eries$/i),                                                     "$1eries"],
77261       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
77262       [(/([lr])ves$/i),                                                    "$1f"    ],
77263       [(/(tive)s$/i),                                                      "$1"     ],
77264       [(/(hive)s$/i),                                                      "$1"     ],
77265       [(/([^f])ves$/i),                                                    "$1fe"   ],
77266       [(/(^analy)ses$/i),                                                  "$1sis"  ],
77267       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
77268       [(/([ti])a$/i),                                                      "$1um"   ],
77269       [(/(n)ews$/i),                                                       "$1ews"  ],
77270       [(/people$/i),                                                       "person" ],
77271       [(/s$/i),                                                            ""       ]
77272     ],
77273
77274     /**
77275      * @private
77276      * The registered uncountable words
77277      * @property {String[]} uncountable
77278      */
77279      uncountable: [
77280         "sheep",
77281         "fish",
77282         "series",
77283         "species",
77284         "money",
77285         "rice",
77286         "information",
77287         "equipment",
77288         "grass",
77289         "mud",
77290         "offspring",
77291         "deer",
77292         "means"
77293     ],
77294
77295     /**
77296      * Adds a new singularization rule to the Inflector. See the intro docs for more information
77297      * @param {RegExp} matcher The matcher regex
77298      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
77299      */
77300     singular: function(matcher, replacer) {
77301         this.singulars.unshift([matcher, replacer]);
77302     },
77303
77304     /**
77305      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
77306      * @param {RegExp} matcher The matcher regex
77307      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
77308      */
77309     plural: function(matcher, replacer) {
77310         this.plurals.unshift([matcher, replacer]);
77311     },
77312
77313     /**
77314      * Removes all registered singularization rules
77315      */
77316     clearSingulars: function() {
77317         this.singulars = [];
77318     },
77319
77320     /**
77321      * Removes all registered pluralization rules
77322      */
77323     clearPlurals: function() {
77324         this.plurals = [];
77325     },
77326
77327     /**
77328      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
77329      * @param {String} word The word to test
77330      * @return {Boolean} True if the word is transnumeral
77331      */
77332     isTransnumeral: function(word) {
77333         return Ext.Array.indexOf(this.uncountable, word) != -1;
77334     },
77335
77336     /**
77337      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
77338      * @param {String} word The word to pluralize
77339      * @return {String} The pluralized form of the word
77340      */
77341     pluralize: function(word) {
77342         if (this.isTransnumeral(word)) {
77343             return word;
77344         }
77345
77346         var plurals = this.plurals,
77347             length  = plurals.length,
77348             tuple, regex, i;
77349
77350         for (i = 0; i < length; i++) {
77351             tuple = plurals[i];
77352             regex = tuple[0];
77353
77354             if (regex == word || (regex.test && regex.test(word))) {
77355                 return word.replace(regex, tuple[1]);
77356             }
77357         }
77358
77359         return word;
77360     },
77361
77362     /**
77363      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
77364      * @param {String} word The word to singularize
77365      * @return {String} The singularized form of the word
77366      */
77367     singularize: function(word) {
77368         if (this.isTransnumeral(word)) {
77369             return word;
77370         }
77371
77372         var singulars = this.singulars,
77373             length    = singulars.length,
77374             tuple, regex, i;
77375
77376         for (i = 0; i < length; i++) {
77377             tuple = singulars[i];
77378             regex = tuple[0];
77379
77380             if (regex == word || (regex.test && regex.test(word))) {
77381                 return word.replace(regex, tuple[1]);
77382             }
77383         }
77384
77385         return word;
77386     },
77387
77388     /**
77389      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
77390      * package
77391      * @param {String} word The word to classify
77392      * @return {String} The classified version of the word
77393      */
77394     classify: function(word) {
77395         return Ext.String.capitalize(this.singularize(word));
77396     },
77397
77398     /**
77399      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
77400      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
77401      * @param {Number} number The number to ordinalize
77402      * @return {String} The ordinalized number
77403      */
77404     ordinalize: function(number) {
77405         var parsed = parseInt(number, 10),
77406             mod10  = parsed % 10,
77407             mod100 = parsed % 100;
77408
77409         //11 through 13 are a special case
77410         if (11 <= mod100 && mod100 <= 13) {
77411             return number + "th";
77412         } else {
77413             switch(mod10) {
77414                 case 1 : return number + "st";
77415                 case 2 : return number + "nd";
77416                 case 3 : return number + "rd";
77417                 default: return number + "th";
77418             }
77419         }
77420     }
77421 }, function() {
77422     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
77423     var irregulars = {
77424             alumnus: 'alumni',
77425             cactus : 'cacti',
77426             focus  : 'foci',
77427             nucleus: 'nuclei',
77428             radius: 'radii',
77429             stimulus: 'stimuli',
77430             ellipsis: 'ellipses',
77431             paralysis: 'paralyses',
77432             oasis: 'oases',
77433             appendix: 'appendices',
77434             index: 'indexes',
77435             beau: 'beaux',
77436             bureau: 'bureaux',
77437             tableau: 'tableaux',
77438             woman: 'women',
77439             child: 'children',
77440             man: 'men',
77441             corpus:     'corpora',
77442             criterion: 'criteria',
77443             curriculum: 'curricula',
77444             genus: 'genera',
77445             memorandum: 'memoranda',
77446             phenomenon: 'phenomena',
77447             foot: 'feet',
77448             goose: 'geese',
77449             tooth: 'teeth',
77450             antenna: 'antennae',
77451             formula: 'formulae',
77452             nebula: 'nebulae',
77453             vertebra: 'vertebrae',
77454             vita: 'vitae'
77455         },
77456         singular;
77457
77458     for (singular in irregulars) {
77459         this.plural(singular, irregulars[singular]);
77460         this.singular(irregulars[singular], singular);
77461     }
77462 });
77463 /**
77464  * @author Ed Spencer
77465  * @class Ext.data.HasManyAssociation
77466  * @extends Ext.data.Association
77467  * 
77468  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
77469  * 
77470 <pre><code>
77471 Ext.define('Product', {
77472     extend: 'Ext.data.Model',
77473     fields: [
77474         {name: 'id',      type: 'int'},
77475         {name: 'user_id', type: 'int'},
77476         {name: 'name',    type: 'string'}
77477     ]
77478 });
77479
77480 Ext.define('User', {
77481     extend: 'Ext.data.Model',
77482     fields: [
77483         {name: 'id',   type: 'int'},
77484         {name: 'name', type: 'string'}
77485     ],
77486     // we can use the hasMany shortcut on the model to create a hasMany association
77487     hasMany: {model: 'Product', name: 'products'}
77488 });
77489 </pre></code>
77490
77491  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
77492  * us a new function on every User instance, in this case the function is called 'products' because that is the name
77493  * we specified in the association configuration above.</p>
77494  * 
77495  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
77496  * only Products for the given model instance:</p>
77497  * 
77498 <pre><code>
77499 //first, we load up a User with id of 1
77500 var user = Ext.create('User', {id: 1, name: 'Ed'});
77501
77502 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
77503 //the created store is automatically scoped to the set of Products for the User with id of 1
77504 var products = user.products();
77505
77506 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
77507 products.add({
77508     name: 'Another Product'
77509 });
77510
77511 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
77512 products.sync();
77513 </code></pre>
77514  * 
77515  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
77516  * though calling products() a second time returns the same store instance.</p>
77517  * 
77518  * <p><u>Custom filtering</u></p>
77519  * 
77520  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
77521  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
77522  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
77523  * 
77524  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
77525  * have models for Search and Tweet:</p>
77526  * 
77527 <pre><code>
77528 Ext.define('Search', {
77529     extend: 'Ext.data.Model',
77530     fields: [
77531         'id', 'query'
77532     ],
77533
77534     hasMany: {
77535         model: 'Tweet',
77536         name : 'tweets',
77537         filterProperty: 'query'
77538     }
77539 });
77540
77541 Ext.define('Tweet', {
77542     extend: 'Ext.data.Model',
77543     fields: [
77544         'id', 'text', 'from_user'
77545     ]
77546 });
77547
77548 //returns a Store filtered by the filterProperty
77549 var store = new Search({query: 'Sencha Touch'}).tweets();
77550 </code></pre>
77551  * 
77552  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
77553  * equivalent to this:</p>
77554  * 
77555 <pre><code>
77556 var store = Ext.create('Ext.data.Store', {
77557     model: 'Tweet',
77558     filters: [
77559         {
77560             property: 'query',
77561             value   : 'Sencha Touch'
77562         }
77563     ]
77564 });
77565 </code></pre>
77566  */
77567 Ext.define('Ext.data.HasManyAssociation', {
77568     extend: 'Ext.data.Association',
77569     requires: ['Ext.util.Inflector'],
77570
77571     alias: 'association.hasmany',
77572
77573     /**
77574      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
77575      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
77576      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
77577      * the store is automatically filtered so that only records with a matching foreign key are included in the 
77578      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
77579      * <pre><code>
77580 Ext.define('Group', {
77581     extend: 'Ext.data.Model',
77582     fields: ['id', 'name'],
77583     hasMany: 'User'
77584 });
77585
77586 Ext.define('User', {
77587     extend: 'Ext.data.Model',
77588     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
77589     belongsTo: 'Group'
77590 });
77591      * </code></pre>
77592      */
77593     
77594     /**
77595      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
77596      * If not specified, the pluralized name of the child model is used.
77597      * <pre><code>
77598 // This will create a users() method on any Group model instance
77599 Ext.define('Group', {
77600     extend: 'Ext.data.Model',
77601     fields: ['id', 'name'],
77602     hasMany: 'User'
77603 });
77604 var group = new Group();
77605 console.log(group.users());
77606
77607 // The method to retrieve the users will now be getUserList
77608 Ext.define('Group', {
77609     extend: 'Ext.data.Model',
77610     fields: ['id', 'name'],
77611     hasMany: {model: 'User', name: 'getUserList'}
77612 });
77613 var group = new Group();
77614 console.log(group.getUserList());
77615      * </code></pre>
77616      */
77617     
77618     /**
77619      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
77620      * undefined.
77621      */
77622     
77623     /**
77624      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
77625      * this is not set, a filter is automatically created which filters the association based on the configured 
77626      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
77627      */
77628     
77629     /**
77630      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
77631      * Defaults to <tt>false</tt>.
77632      */
77633     
77634     /**
77635      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
77636      * Use 'hasMany' to create a HasManyAssocation
77637      * <pre><code>
77638 associations: [{
77639     type: 'hasMany',
77640     model: 'User'
77641 }]
77642      * </code></pre>
77643      */
77644     
77645     constructor: function(config) {
77646         var me = this,
77647             ownerProto,
77648             name;
77649             
77650         me.callParent(arguments);
77651         
77652         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
77653         
77654         ownerProto = me.ownerModel.prototype;
77655         name = me.name;
77656         
77657         Ext.applyIf(me, {
77658             storeName : name + "Store",
77659             foreignKey: me.ownerName.toLowerCase() + "_id"
77660         });
77661         
77662         ownerProto[name] = me.createStore();
77663     },
77664     
77665     /**
77666      * @private
77667      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
77668      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
77669      * returns a Store configured to return the filtered set of a single Group's Users.
77670      * @return {Function} The store-generating function
77671      */
77672     createStore: function() {
77673         var that            = this,
77674             associatedModel = that.associatedModel,
77675             storeName       = that.storeName,
77676             foreignKey      = that.foreignKey,
77677             primaryKey      = that.primaryKey,
77678             filterProperty  = that.filterProperty,
77679             autoLoad        = that.autoLoad,
77680             storeConfig     = that.storeConfig || {};
77681         
77682         return function() {
77683             var me = this,
77684                 config, filter,
77685                 modelDefaults = {};
77686                 
77687             if (me[storeName] === undefined) {
77688                 if (filterProperty) {
77689                     filter = {
77690                         property  : filterProperty,
77691                         value     : me.get(filterProperty),
77692                         exactMatch: true
77693                     };
77694                 } else {
77695                     filter = {
77696                         property  : foreignKey,
77697                         value     : me.get(primaryKey),
77698                         exactMatch: true
77699                     };
77700                 }
77701                 
77702                 modelDefaults[foreignKey] = me.get(primaryKey);
77703                 
77704                 config = Ext.apply({}, storeConfig, {
77705                     model        : associatedModel,
77706                     filters      : [filter],
77707                     remoteFilter : false,
77708                     modelDefaults: modelDefaults
77709                 });
77710                 
77711                 me[storeName] = Ext.create('Ext.data.Store', config);
77712                 if (autoLoad) {
77713                     me[storeName].load();
77714                 }
77715             }
77716             
77717             return me[storeName];
77718         };
77719     },
77720     
77721     /**
77722      * Read associated data
77723      * @private
77724      * @param {Ext.data.Model} record The record we're writing to
77725      * @param {Ext.data.reader.Reader} reader The reader for the associated model
77726      * @param {Object} associationData The raw associated data
77727      */
77728     read: function(record, reader, associationData){
77729         var store = record[this.name](),
77730             inverse;
77731     
77732         store.add(reader.read(associationData).records);
77733     
77734         //now that we've added the related records to the hasMany association, set the inverse belongsTo
77735         //association on each of them if it exists
77736         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
77737             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
77738         });
77739     
77740         //if the inverse association was found, set it now on each record we've just created
77741         if (inverse) {
77742             store.data.each(function(associatedRecord){
77743                 associatedRecord[inverse.instanceName] = record;
77744             });
77745         }
77746     }
77747 });
77748 /**
77749  * @class Ext.data.JsonP
77750  * @singleton
77751  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
77752  * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
77753  */
77754 Ext.define('Ext.data.JsonP', {
77755
77756     /* Begin Definitions */
77757
77758     singleton: true,
77759
77760     statics: {
77761         requestCount: 0,
77762         requests: {}
77763     },
77764
77765     /* End Definitions */
77766
77767     /**
77768      * @property timeout
77769      * @type Number
77770      * A default timeout for any JsonP requests. If the request has not completed in this time the
77771      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
77772      */
77773     timeout: 30000,
77774
77775     /**
77776      * @property disableCaching
77777      * @type Boolean
77778      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
77779      */
77780     disableCaching: true,
77781
77782     /**
77783      * @property disableCachingParam
77784      * @type String
77785      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
77786      */
77787     disableCachingParam: '_dc',
77788
77789     /**
77790      * @property callbackKey
77791      * @type String
77792      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
77793      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
77794      * url?callback=Ext.data.JsonP.callback1
77795      */
77796     callbackKey: 'callback',
77797
77798     /**
77799      * Makes a JSONP request.
77800      * @param {Object} options An object which may contain the following properties. Note that options will
77801      * take priority over any defaults that are specified in the class.
77802      * <ul>
77803      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
77804      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
77805      * key value pairs that will be sent along with the request.</div></li>
77806      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
77807      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
77808      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
77809      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
77810      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
77811      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
77812      * in each JsonP request that you make.</div></li>
77813      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
77814      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
77815      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
77816      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
77817      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
77818      * completes, whether it is a success or failure.</div></li>
77819      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
77820      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
77821      * </ul>
77822      * @return {Object} request An object containing the request details.
77823      */
77824     request: function(options){
77825         options = Ext.apply({}, options);
77826
77827         if (!options.url) {
77828             Ext.Error.raise('A url must be specified for a JSONP request.');
77829         }
77830
77831         var me = this,
77832             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
77833             cacheParam = options.disableCachingParam || me.disableCachingParam,
77834             id = ++me.statics().requestCount,
77835             callbackName = options.callbackName || 'callback' + id,
77836             callbackKey = options.callbackKey || me.callbackKey,
77837             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
77838             params = Ext.apply({}, options.params),
77839             url = options.url,
77840             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
77841             request,
77842             script;
77843
77844         params[callbackKey] = name + '.data.JsonP.' + callbackName;
77845         if (disableCaching) {
77846             params[cacheParam] = new Date().getTime();
77847         }
77848
77849         script = me.createScript(url, params);
77850
77851         me.statics().requests[id] = request = {
77852             url: url,
77853             params: params,
77854             script: script,
77855             id: id,
77856             scope: options.scope,
77857             success: options.success,
77858             failure: options.failure,
77859             callback: options.callback,
77860             callbackName: callbackName
77861         };
77862
77863         if (timeout > 0) {
77864             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
77865         }
77866
77867         me.setupErrorHandling(request);
77868         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
77869         Ext.getHead().appendChild(script);
77870         return request;
77871     },
77872
77873     /**
77874      * Abort a request. If the request parameter is not specified all open requests will
77875      * be aborted.
77876      * @param {Object/String} request (Optional) The request to abort
77877      */
77878     abort: function(request){
77879         var requests = this.statics().requests,
77880             key;
77881
77882         if (request) {
77883             if (!request.id) {
77884                 request = requests[request];
77885             }
77886             this.abort(request);
77887         } else {
77888             for (key in requests) {
77889                 if (requests.hasOwnProperty(key)) {
77890                     this.abort(requests[key]);
77891                 }
77892             }
77893         }
77894     },
77895
77896     /**
77897      * Sets up error handling for the script
77898      * @private
77899      * @param {Object} request The request
77900      */
77901     setupErrorHandling: function(request){
77902         request.script.onerror = Ext.bind(this.handleError, this, [request]);
77903     },
77904
77905     /**
77906      * Handles any aborts when loading the script
77907      * @private
77908      * @param {Object} request The request
77909      */
77910     handleAbort: function(request){
77911         request.errorType = 'abort';
77912         this.handleResponse(null, request);
77913     },
77914
77915     /**
77916      * Handles any script errors when loading the script
77917      * @private
77918      * @param {Object} request The request
77919      */
77920     handleError: function(request){
77921         request.errorType = 'error';
77922         this.handleResponse(null, request);
77923     },
77924
77925     /**
77926      * Cleans up anu script handling errors
77927      * @private
77928      * @param {Object} request The request
77929      */
77930     cleanupErrorHandling: function(request){
77931         request.script.onerror = null;
77932     },
77933
77934     /**
77935      * Handle any script timeouts
77936      * @private
77937      * @param {Object} request The request
77938      */
77939     handleTimeout: function(request){
77940         request.errorType = 'timeout';
77941         this.handleResponse(null, request);
77942     },
77943
77944     /**
77945      * Handle a successful response
77946      * @private
77947      * @param {Object} result The result from the request
77948      * @param {Object} request The request
77949      */
77950     handleResponse: function(result, request){
77951
77952         var success = true;
77953
77954         if (request.timeout) {
77955             clearTimeout(request.timeout);
77956         }
77957         delete this[request.callbackName];
77958         delete this.statics()[request.id];
77959         this.cleanupErrorHandling(request);
77960         Ext.fly(request.script).remove();
77961
77962         if (request.errorType) {
77963             success = false;
77964             Ext.callback(request.failure, request.scope, [request.errorType]);
77965         } else {
77966             Ext.callback(request.success, request.scope, [result]);
77967         }
77968         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
77969     },
77970
77971     /**
77972      * Create the script tag
77973      * @private
77974      * @param {String} url The url of the request
77975      * @param {Object} params Any extra params to be sent
77976      */
77977     createScript: function(url, params) {
77978         var script = document.createElement('script');
77979         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
77980         script.setAttribute("async", true);
77981         script.setAttribute("type", "text/javascript");
77982         return script;
77983     }
77984 });
77985
77986 /**
77987  * @class Ext.data.JsonPStore
77988  * @extends Ext.data.Store
77989  * @private
77990  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
77991  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
77992  * <p>A store configuration would be something like:<pre><code>
77993 var store = new Ext.data.JsonPStore({
77994     // store configs
77995     autoDestroy: true,
77996     storeId: 'myStore',
77997
77998     // proxy configs
77999     url: 'get-images.php',
78000
78001     // reader configs
78002     root: 'images',
78003     idProperty: 'name',
78004     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
78005 });
78006  * </code></pre></p>
78007  * <p>This store is configured to consume a returned object of the form:<pre><code>
78008 stcCallback({
78009     images: [
78010         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
78011         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
78012     ]
78013 })
78014  * </code></pre>
78015  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
78016  * for details of how this works.</p>
78017  * An object literal of this form could also be used as the {@link #data} config option.</p>
78018  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
78019  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
78020  * @xtype jsonpstore
78021  */
78022 Ext.define('Ext.data.JsonPStore', {
78023     extend: 'Ext.data.Store',
78024     alias : 'store.jsonp',
78025
78026     /**
78027      * @cfg {Ext.data.DataReader} reader @hide
78028      */
78029     constructor: function(config) {
78030         this.callParent(Ext.apply(config, {
78031             reader: Ext.create('Ext.data.reader.Json', config),
78032             proxy : Ext.create('Ext.data.proxy.JsonP', config)
78033         }));
78034     }
78035 });
78036
78037 /**
78038  * This class is used as a set of methods that are applied to the prototype of a
78039  * Model to decorate it with a Node API. This means that models used in conjunction with a tree
78040  * will have all of the tree related methods available on the model. In general this class will
78041  * not be used directly by the developer. This class also creates extra fields on the model if
78042  * they do not exist, to help maintain the tree state and UI. These fields are documented as
78043  * config options.
78044  */
78045 Ext.define('Ext.data.NodeInterface', {
78046     requires: ['Ext.data.Field'],
78047
78048     /**
78049      * @cfg {String} parentId
78050      * ID of parent node.
78051      */
78052
78053     /**
78054      * @cfg {Number} index
78055      * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
78056      * index will be 2.
78057      */
78058
78059     /**
78060      * @cfg {Number} depth
78061      * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
78062      */
78063
78064     /**
78065      * @cfg {Boolean} [expanded=false]
78066      * True if the node is expanded.
78067      */
78068
78069     /**
78070      * @cfg {Boolean} [expandable=false]
78071      * Set to true to allow for expanding/collapsing of this node.
78072      */
78073
78074     /**
78075      * @cfg {Boolean} [checked=null]
78076      * Set to true or false to show a checkbox alongside this node.
78077      */
78078
78079     /**
78080      * @cfg {Boolean} [leaf=false]
78081      * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
78082      * rendered for this node.
78083      */
78084
78085     /**
78086      * @cfg {String} cls
78087      * CSS class to apply for this node.
78088      */
78089
78090     /**
78091      * @cfg {String} iconCls
78092      * CSS class to apply for this node's icon.
78093      */
78094
78095     /**
78096      * @cfg {String} icon
78097      * URL for this node's icon.
78098      */
78099
78100     /**
78101      * @cfg {Boolean} root
78102      * True if this is the root node.
78103      */
78104
78105     /**
78106      * @cfg {Boolean} isLast
78107      * True if this is the last node.
78108      */
78109
78110     /**
78111      * @cfg {Boolean} isFirst
78112      * True if this is the first node.
78113      */
78114
78115     /**
78116      * @cfg {Boolean} [allowDrop=true]
78117      * Set to false to deny dropping on this node.
78118      */
78119
78120     /**
78121      * @cfg {Boolean} [allowDrag=true]
78122      * Set to false to deny dragging of this node.
78123      */
78124
78125     /**
78126      * @cfg {Boolean} [loaded=false]
78127      * True if the node has finished loading.
78128      */
78129
78130     /**
78131      * @cfg {Boolean} [loading=false]
78132      * True if the node is currently loading.
78133      */
78134
78135     /**
78136      * @cfg {String} href
78137      * An URL for a link that's created when this config is specified.
78138      */
78139
78140     /**
78141      * @cfg {String} hrefTarget
78142      * Target for link. Only applicable when {@link #href} also specified.
78143      */
78144
78145     /**
78146      * @cfg {String} qtip
78147      * Tooltip text to show on this node.
78148      */
78149
78150     /**
78151      * @cfg {String} qtitle
78152      * Tooltip title.
78153      */
78154
78155     /**
78156      * @cfg {String} text
78157      * The text for to show on node label.
78158      */
78159
78160     /**
78161      * @cfg {Ext.data.NodeInterface[]} children
78162      * Array of child nodes.
78163      */
78164
78165
78166     /**
78167      * @property nextSibling
78168      * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
78169      */
78170
78171     /**
78172      * @property previousSibling
78173      * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
78174      */
78175
78176     /**
78177      * @property parentNode
78178      * A reference to this node's parent node. `null` if this node is the root node.
78179      */
78180
78181     /**
78182      * @property lastChild
78183      * A reference to this node's last child node. `null` if this node has no children.
78184      */
78185
78186     /**
78187      * @property firstChild
78188      * A reference to this node's first child node. `null` if this node has no children.
78189      */
78190
78191     /**
78192      * @property childNodes
78193      * An array of this nodes children.  Array will be empty if this node has no chidren.
78194      */
78195
78196     statics: {
78197         /**
78198          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
78199          * This adds a set of methods, new events, new properties and new fields on every Record
78200          * with the same Model as the passed Record.
78201          * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
78202          * @static
78203          */
78204         decorate: function(record) {
78205             if (!record.isNode) {
78206                 // Apply the methods and fields to the prototype
78207                 // @TODO: clean this up to use proper class system stuff
78208                 var mgr = Ext.ModelManager,
78209                     modelName = record.modelName,
78210                     modelClass = mgr.getModel(modelName),
78211                     idName = modelClass.prototype.idProperty,
78212                     newFields = [],
78213                     i, newField, len;
78214
78215                 // Start by adding the NodeInterface methods to the Model's prototype
78216                 modelClass.override(this.getPrototypeBody());
78217                 newFields = this.applyFields(modelClass, [
78218                     {name: idName,       type: 'string',  defaultValue: null},
78219                     {name: 'parentId',   type: 'string',  defaultValue: null},
78220                     {name: 'index',      type: 'int',     defaultValue: null},
78221                     {name: 'depth',      type: 'int',     defaultValue: 0},
78222                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
78223                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
78224                     {name: 'checked',    type: 'auto',    defaultValue: null},
78225                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
78226                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
78227                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
78228                     {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
78229                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
78230                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
78231                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
78232                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
78233                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
78234                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
78235                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
78236                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
78237                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
78238                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
78239                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
78240                 ]);
78241
78242                 len = newFields.length;
78243                 // Set default values
78244                 for (i = 0; i < len; ++i) {
78245                     newField = newFields[i];
78246                     if (record.get(newField.name) === undefined) {
78247                         record.data[newField.name] = newField.defaultValue;
78248                     }
78249                 }
78250             }
78251
78252             Ext.applyIf(record, {
78253                 firstChild: null,
78254                 lastChild: null,
78255                 parentNode: null,
78256                 previousSibling: null,
78257                 nextSibling: null,
78258                 childNodes: []
78259             });
78260             // Commit any fields so the record doesn't show as dirty initially
78261             record.commit(true);
78262
78263             record.enableBubble([
78264                 /**
78265                  * @event append
78266                  * Fires when a new child node is appended
78267                  * @param {Ext.data.NodeInterface} this This node
78268                  * @param {Ext.data.NodeInterface} node The newly appended node
78269                  * @param {Number} index The index of the newly appended node
78270                  */
78271                 "append",
78272
78273                 /**
78274                  * @event remove
78275                  * Fires when a child node is removed
78276                  * @param {Ext.data.NodeInterface} this This node
78277                  * @param {Ext.data.NodeInterface} node The removed node
78278                  */
78279                 "remove",
78280
78281                 /**
78282                  * @event move
78283                  * Fires when this node is moved to a new location in the tree
78284                  * @param {Ext.data.NodeInterface} this This node
78285                  * @param {Ext.data.NodeInterface} oldParent The old parent of this node
78286                  * @param {Ext.data.NodeInterface} newParent The new parent of this node
78287                  * @param {Number} index The index it was moved to
78288                  */
78289                 "move",
78290
78291                 /**
78292                  * @event insert
78293                  * Fires when a new child node is inserted.
78294                  * @param {Ext.data.NodeInterface} this This node
78295                  * @param {Ext.data.NodeInterface} node The child node inserted
78296                  * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
78297                  */
78298                 "insert",
78299
78300                 /**
78301                  * @event beforeappend
78302                  * Fires before a new child is appended, return false to cancel the append.
78303                  * @param {Ext.data.NodeInterface} this This node
78304                  * @param {Ext.data.NodeInterface} node The child node to be appended
78305                  */
78306                 "beforeappend",
78307
78308                 /**
78309                  * @event beforeremove
78310                  * Fires before a child is removed, return false to cancel the remove.
78311                  * @param {Ext.data.NodeInterface} this This node
78312                  * @param {Ext.data.NodeInterface} node The child node to be removed
78313                  */
78314                 "beforeremove",
78315
78316                 /**
78317                  * @event beforemove
78318                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
78319                  * @param {Ext.data.NodeInterface} this This node
78320                  * @param {Ext.data.NodeInterface} oldParent The parent of this node
78321                  * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
78322                  * @param {Number} index The index it is being moved to
78323                  */
78324                 "beforemove",
78325
78326                  /**
78327                   * @event beforeinsert
78328                   * Fires before a new child is inserted, return false to cancel the insert.
78329                   * @param {Ext.data.NodeInterface} this This node
78330                   * @param {Ext.data.NodeInterface} node The child node to be inserted
78331                   * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
78332                   */
78333                 "beforeinsert",
78334
78335                 /**
78336                  * @event expand
78337                  * Fires when this node is expanded.
78338                  * @param {Ext.data.NodeInterface} this The expanding node
78339                  */
78340                 "expand",
78341
78342                 /**
78343                  * @event collapse
78344                  * Fires when this node is collapsed.
78345                  * @param {Ext.data.NodeInterface} this The collapsing node
78346                  */
78347                 "collapse",
78348
78349                 /**
78350                  * @event beforeexpand
78351                  * Fires before this node is expanded.
78352                  * @param {Ext.data.NodeInterface} this The expanding node
78353                  */
78354                 "beforeexpand",
78355
78356                 /**
78357                  * @event beforecollapse
78358                  * Fires before this node is collapsed.
78359                  * @param {Ext.data.NodeInterface} this The collapsing node
78360                  */
78361                 "beforecollapse",
78362
78363                 /**
78364                  * @event sort
78365                  * Fires when this node's childNodes are sorted.
78366                  * @param {Ext.data.NodeInterface} this This node.
78367                  * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
78368                  */
78369                 "sort"
78370             ]);
78371
78372             return record;
78373         },
78374
78375         applyFields: function(modelClass, addFields) {
78376             var modelPrototype = modelClass.prototype,
78377                 fields = modelPrototype.fields,
78378                 keys = fields.keys,
78379                 ln = addFields.length,
78380                 addField, i, name,
78381                 newFields = [];
78382
78383             for (i = 0; i < ln; i++) {
78384                 addField = addFields[i];
78385                 if (!Ext.Array.contains(keys, addField.name)) {
78386                     addField = Ext.create('data.field', addField);
78387
78388                     newFields.push(addField);
78389                     fields.add(addField);
78390                 }
78391             }
78392
78393             return newFields;
78394         },
78395
78396         getPrototypeBody: function() {
78397             return {
78398                 isNode: true,
78399
78400                 /**
78401                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
78402                  * @return {Boolean}
78403                  */
78404                 createNode: function(node) {
78405                     if (Ext.isObject(node) && !node.isModel) {
78406                         node = Ext.ModelManager.create(node, this.modelName);
78407                     }
78408                     // Make sure the node implements the node interface
78409                     return Ext.data.NodeInterface.decorate(node);
78410                 },
78411
78412                 /**
78413                  * Returns true if this node is a leaf
78414                  * @return {Boolean}
78415                  */
78416                 isLeaf : function() {
78417                     return this.get('leaf') === true;
78418                 },
78419
78420                 /**
78421                  * Sets the first child of this node
78422                  * @private
78423                  * @param {Ext.data.NodeInterface} node
78424                  */
78425                 setFirstChild : function(node) {
78426                     this.firstChild = node;
78427                 },
78428
78429                 /**
78430                  * Sets the last child of this node
78431                  * @private
78432                  * @param {Ext.data.NodeInterface} node
78433                  */
78434                 setLastChild : function(node) {
78435                     this.lastChild = node;
78436                 },
78437
78438                 /**
78439                  * Updates general data of this node like isFirst, isLast, depth. This
78440                  * method is internally called after a node is moved. This shouldn't
78441                  * have to be called by the developer unless they are creating custom
78442                  * Tree plugins.
78443                  * @return {Boolean}
78444                  */
78445                 updateInfo: function(silent) {
78446                     var me = this,
78447                         isRoot = me.isRoot(),
78448                         parentNode = me.parentNode,
78449                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
78450                         isLast = (!parentNode ? true : parentNode.lastChild == me),
78451                         depth = 0,
78452                         parent = me,
78453                         children = me.childNodes,
78454                         len = children.length,
78455                         i = 0;
78456
78457                     while (parent.parentNode) {
78458                         ++depth;
78459                         parent = parent.parentNode;
78460                     }
78461
78462                     me.beginEdit();
78463                     me.set({
78464                         isFirst: isFirst,
78465                         isLast: isLast,
78466                         depth: depth,
78467                         index: parentNode ? parentNode.indexOf(me) : 0,
78468                         parentId: parentNode ? parentNode.getId() : null
78469                     });
78470                     me.endEdit(silent);
78471                     if (silent) {
78472                         me.commit();
78473                     }
78474
78475                     for (i = 0; i < len; i++) {
78476                         children[i].updateInfo(silent);
78477                     }
78478                 },
78479
78480                 /**
78481                  * Returns true if this node is the last child of its parent
78482                  * @return {Boolean}
78483                  */
78484                 isLast : function() {
78485                    return this.get('isLast');
78486                 },
78487
78488                 /**
78489                  * Returns true if this node is the first child of its parent
78490                  * @return {Boolean}
78491                  */
78492                 isFirst : function() {
78493                    return this.get('isFirst');
78494                 },
78495
78496                 /**
78497                  * Returns true if this node has one or more child nodes, else false.
78498                  * @return {Boolean}
78499                  */
78500                 hasChildNodes : function() {
78501                     return !this.isLeaf() && this.childNodes.length > 0;
78502                 },
78503
78504                 /**
78505                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
78506                  * node attribute is explicitly specified as true, otherwise returns false.
78507                  * @return {Boolean}
78508                  */
78509                 isExpandable : function() {
78510                     var me = this;
78511
78512                     if (me.get('expandable')) {
78513                         return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
78514                     }
78515                     return false;
78516                 },
78517
78518                 /**
78519                  * Inserts node(s) as the last child node of this node.
78520                  *
78521                  * If the node was previously a child node of another parent node, it will be removed from that node first.
78522                  *
78523                  * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
78524                  * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
78525                  */
78526                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
78527                     var me = this,
78528                         i, ln,
78529                         index,
78530                         oldParent,
78531                         ps;
78532
78533                     // if passed an array or multiple args do them one by one
78534                     if (Ext.isArray(node)) {
78535                         for (i = 0, ln = node.length; i < ln; i++) {
78536                             me.appendChild(node[i]);
78537                         }
78538                     } else {
78539                         // Make sure it is a record
78540                         node = me.createNode(node);
78541
78542                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
78543                             return false;
78544                         }
78545
78546                         index = me.childNodes.length;
78547                         oldParent = node.parentNode;
78548
78549                         // it's a move, make sure we move it cleanly
78550                         if (oldParent) {
78551                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
78552                                 return false;
78553                             }
78554                             oldParent.removeChild(node, null, false, true);
78555                         }
78556
78557                         index = me.childNodes.length;
78558                         if (index === 0) {
78559                             me.setFirstChild(node);
78560                         }
78561
78562                         me.childNodes.push(node);
78563                         node.parentNode = me;
78564                         node.nextSibling = null;
78565
78566                         me.setLastChild(node);
78567
78568                         ps = me.childNodes[index - 1];
78569                         if (ps) {
78570                             node.previousSibling = ps;
78571                             ps.nextSibling = node;
78572                             ps.updateInfo(suppressNodeUpdate);
78573                         } else {
78574                             node.previousSibling = null;
78575                         }
78576
78577                         node.updateInfo(suppressNodeUpdate);
78578
78579                         // As soon as we append a child to this node, we are loaded
78580                         if (!me.isLoaded()) {
78581                             me.set('loaded', true);
78582                         }
78583                         // If this node didnt have any childnodes before, update myself
78584                         else if (me.childNodes.length === 1) {
78585                             me.set('loaded', me.isLoaded());
78586                         }
78587
78588                         if (suppressEvents !== true) {
78589                             me.fireEvent("append", me, node, index);
78590
78591                             if (oldParent) {
78592                                 node.fireEvent("move", node, oldParent, me, index);
78593                             }
78594                         }
78595
78596                         return node;
78597                     }
78598                 },
78599
78600                 /**
78601                  * Returns the bubble target for this node
78602                  * @private
78603                  * @return {Object} The bubble target
78604                  */
78605                 getBubbleTarget: function() {
78606                     return this.parentNode;
78607                 },
78608
78609                 /**
78610                  * Removes a child node from this node.
78611                  * @param {Ext.data.NodeInterface} node The node to remove
78612                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78613                  * @return {Ext.data.NodeInterface} The removed node
78614                  */
78615                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
78616                     var me = this,
78617                         index = me.indexOf(node);
78618
78619                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
78620                         return false;
78621                     }
78622
78623                     // remove it from childNodes collection
78624                     Ext.Array.erase(me.childNodes, index, 1);
78625
78626                     // update child refs
78627                     if (me.firstChild == node) {
78628                         me.setFirstChild(node.nextSibling);
78629                     }
78630                     if (me.lastChild == node) {
78631                         me.setLastChild(node.previousSibling);
78632                     }
78633
78634                     // update siblings
78635                     if (node.previousSibling) {
78636                         node.previousSibling.nextSibling = node.nextSibling;
78637                         node.previousSibling.updateInfo(suppressNodeUpdate);
78638                     }
78639                     if (node.nextSibling) {
78640                         node.nextSibling.previousSibling = node.previousSibling;
78641                         node.nextSibling.updateInfo(suppressNodeUpdate);
78642                     }
78643
78644                     if (suppressEvents !== true) {
78645                         me.fireEvent("remove", me, node);
78646                     }
78647
78648
78649                     // If this node suddenly doesnt have childnodes anymore, update myself
78650                     if (!me.childNodes.length) {
78651                         me.set('loaded', me.isLoaded());
78652                     }
78653
78654                     if (destroy) {
78655                         node.destroy(true);
78656                     } else {
78657                         node.clear();
78658                     }
78659
78660                     return node;
78661                 },
78662
78663                 /**
78664                  * Creates a copy (clone) of this Node.
78665                  * @param {String} [id] A new id, defaults to this Node's id.
78666                  * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
78667                  * False to copy without child Nodes.
78668                  * @return {Ext.data.NodeInterface} A copy of this Node.
78669                  */
78670                 copy: function(newId, deep) {
78671                     var me = this,
78672                         result = me.callOverridden(arguments),
78673                         len = me.childNodes ? me.childNodes.length : 0,
78674                         i;
78675
78676                     // Move child nodes across to the copy if required
78677                     if (deep) {
78678                         for (i = 0; i < len; i++) {
78679                             result.appendChild(me.childNodes[i].copy(true));
78680                         }
78681                     }
78682                     return result;
78683                 },
78684
78685                 /**
78686                  * Clears the node.
78687                  * @private
78688                  * @param {Boolean} [destroy=false] True to destroy the node.
78689                  */
78690                 clear : function(destroy) {
78691                     var me = this;
78692
78693                     // clear any references from the node
78694                     me.parentNode = me.previousSibling = me.nextSibling = null;
78695                     if (destroy) {
78696                         me.firstChild = me.lastChild = null;
78697                     }
78698                 },
78699
78700                 /**
78701                  * Destroys the node.
78702                  */
78703                 destroy : function(silent) {
78704                     /*
78705                      * Silent is to be used in a number of cases
78706                      * 1) When setRoot is called.
78707                      * 2) When destroy on the tree is called
78708                      * 3) For destroying child nodes on a node
78709                      */
78710                     var me = this,
78711                         options = me.destroyOptions;
78712
78713                     if (silent === true) {
78714                         me.clear(true);
78715                         Ext.each(me.childNodes, function(n) {
78716                             n.destroy(true);
78717                         });
78718                         me.childNodes = null;
78719                         delete me.destroyOptions;
78720                         me.callOverridden([options]);
78721                     } else {
78722                         me.destroyOptions = silent;
78723                         // overridden method will be called, since remove will end up calling destroy(true);
78724                         me.remove(true);
78725                     }
78726                 },
78727
78728                 /**
78729                  * Inserts the first node before the second node in this nodes childNodes collection.
78730                  * @param {Ext.data.NodeInterface} node The node to insert
78731                  * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
78732                  * @return {Ext.data.NodeInterface} The inserted node
78733                  */
78734                 insertBefore : function(node, refNode, suppressEvents) {
78735                     var me = this,
78736                         index     = me.indexOf(refNode),
78737                         oldParent = node.parentNode,
78738                         refIndex  = index,
78739                         ps;
78740
78741                     if (!refNode) { // like standard Dom, refNode can be null for append
78742                         return me.appendChild(node);
78743                     }
78744
78745                     // nothing to do
78746                     if (node == refNode) {
78747                         return false;
78748                     }
78749
78750                     // Make sure it is a record with the NodeInterface
78751                     node = me.createNode(node);
78752
78753                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
78754                         return false;
78755                     }
78756
78757                     // when moving internally, indexes will change after remove
78758                     if (oldParent == me && me.indexOf(node) < index) {
78759                         refIndex--;
78760                     }
78761
78762                     // it's a move, make sure we move it cleanly
78763                     if (oldParent) {
78764                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
78765                             return false;
78766                         }
78767                         oldParent.removeChild(node);
78768                     }
78769
78770                     if (refIndex === 0) {
78771                         me.setFirstChild(node);
78772                     }
78773
78774                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
78775                     node.parentNode = me;
78776
78777                     node.nextSibling = refNode;
78778                     refNode.previousSibling = node;
78779
78780                     ps = me.childNodes[refIndex - 1];
78781                     if (ps) {
78782                         node.previousSibling = ps;
78783                         ps.nextSibling = node;
78784                         ps.updateInfo();
78785                     } else {
78786                         node.previousSibling = null;
78787                     }
78788
78789                     node.updateInfo();
78790
78791                     if (!me.isLoaded()) {
78792                         me.set('loaded', true);
78793                     }
78794                     // If this node didnt have any childnodes before, update myself
78795                     else if (me.childNodes.length === 1) {
78796                         me.set('loaded', me.isLoaded());
78797                     }
78798
78799                     if (suppressEvents !== true) {
78800                         me.fireEvent("insert", me, node, refNode);
78801
78802                         if (oldParent) {
78803                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
78804                         }
78805                     }
78806
78807                     return node;
78808                 },
78809
78810                 /**
78811                  * Insert a node into this node
78812                  * @param {Number} index The zero-based index to insert the node at
78813                  * @param {Ext.data.Model} node The node to insert
78814                  * @return {Ext.data.Model} The record you just inserted
78815                  */
78816                 insertChild: function(index, node) {
78817                     var sibling = this.childNodes[index];
78818                     if (sibling) {
78819                         return this.insertBefore(node, sibling);
78820                     }
78821                     else {
78822                         return this.appendChild(node);
78823                     }
78824                 },
78825
78826                 /**
78827                  * Removes this node from its parent
78828                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78829                  * @return {Ext.data.NodeInterface} this
78830                  */
78831                 remove : function(destroy, suppressEvents) {
78832                     var parentNode = this.parentNode;
78833
78834                     if (parentNode) {
78835                         parentNode.removeChild(this, destroy, suppressEvents, true);
78836                     }
78837                     return this;
78838                 },
78839
78840                 /**
78841                  * Removes all child nodes from this node.
78842                  * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
78843                  * @return {Ext.data.NodeInterface} this
78844                  */
78845                 removeAll : function(destroy, suppressEvents) {
78846                     var cn = this.childNodes,
78847                         n;
78848
78849                     while ((n = cn[0])) {
78850                         this.removeChild(n, destroy, suppressEvents);
78851                     }
78852                     return this;
78853                 },
78854
78855                 /**
78856                  * Returns the child node at the specified index.
78857                  * @param {Number} index
78858                  * @return {Ext.data.NodeInterface}
78859                  */
78860                 getChildAt : function(index) {
78861                     return this.childNodes[index];
78862                 },
78863
78864                 /**
78865                  * Replaces one child node in this node with another.
78866                  * @param {Ext.data.NodeInterface} newChild The replacement node
78867                  * @param {Ext.data.NodeInterface} oldChild The node to replace
78868                  * @return {Ext.data.NodeInterface} The replaced node
78869                  */
78870                 replaceChild : function(newChild, oldChild, suppressEvents) {
78871                     var s = oldChild ? oldChild.nextSibling : null;
78872
78873                     this.removeChild(oldChild, suppressEvents);
78874                     this.insertBefore(newChild, s, suppressEvents);
78875                     return oldChild;
78876                 },
78877
78878                 /**
78879                  * Returns the index of a child node
78880                  * @param {Ext.data.NodeInterface} node
78881                  * @return {Number} The index of the node or -1 if it was not found
78882                  */
78883                 indexOf : function(child) {
78884                     return Ext.Array.indexOf(this.childNodes, child);
78885                 },
78886
78887                 /**
78888                  * Gets the hierarchical path from the root of the current node.
78889                  * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
78890                  * @param {String} [separator="/"] A separator to use.
78891                  * @return {String} The node path
78892                  */
78893                 getPath: function(field, separator) {
78894                     field = field || this.idProperty;
78895                     separator = separator || '/';
78896
78897                     var path = [this.get(field)],
78898                         parent = this.parentNode;
78899
78900                     while (parent) {
78901                         path.unshift(parent.get(field));
78902                         parent = parent.parentNode;
78903                     }
78904                     return separator + path.join(separator);
78905                 },
78906
78907                 /**
78908                  * Returns depth of this node (the root node has a depth of 0)
78909                  * @return {Number}
78910                  */
78911                 getDepth : function() {
78912                     return this.get('depth');
78913                 },
78914
78915                 /**
78916                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
78917                  * will be the args provided or the current node. If the function returns false at any point,
78918                  * the bubble is stopped.
78919                  * @param {Function} fn The function to call
78920                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78921                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78922                  */
78923                 bubble : function(fn, scope, args) {
78924                     var p = this;
78925                     while (p) {
78926                         if (fn.apply(scope || p, args || [p]) === false) {
78927                             break;
78928                         }
78929                         p = p.parentNode;
78930                     }
78931                 },
78932
78933                 cascade: function() {
78934                     if (Ext.isDefined(Ext.global.console)) {
78935                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
78936                     }
78937                     return this.cascadeBy.apply(this, arguments);
78938                 },
78939
78940                 /**
78941                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
78942                  * will be the args provided or the current node. If the function returns false at any point,
78943                  * the cascade is stopped on that branch.
78944                  * @param {Function} fn The function to call
78945                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78946                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78947                  */
78948                 cascadeBy : function(fn, scope, args) {
78949                     if (fn.apply(scope || this, args || [this]) !== false) {
78950                         var childNodes = this.childNodes,
78951                             length     = childNodes.length,
78952                             i;
78953
78954                         for (i = 0; i < length; i++) {
78955                             childNodes[i].cascadeBy(fn, scope, args);
78956                         }
78957                     }
78958                 },
78959
78960                 /**
78961                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
78962                  * will be the args provided or the current node. If the function returns false at any point,
78963                  * the iteration stops.
78964                  * @param {Function} fn The function to call
78965                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
78966                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78967                  */
78968                 eachChild : function(fn, scope, args) {
78969                     var childNodes = this.childNodes,
78970                         length     = childNodes.length,
78971                         i;
78972
78973                     for (i = 0; i < length; i++) {
78974                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
78975                             break;
78976                         }
78977                     }
78978                 },
78979
78980                 /**
78981                  * Finds the first child that has the attribute with the specified value.
78982                  * @param {String} attribute The attribute name
78983                  * @param {Object} value The value to search for
78984                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78985                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78986                  */
78987                 findChild : function(attribute, value, deep) {
78988                     return this.findChildBy(function() {
78989                         return this.get(attribute) == value;
78990                     }, null, deep);
78991                 },
78992
78993                 /**
78994                  * Finds the first child by a custom function. The child matches if the function passed returns true.
78995                  * @param {Function} fn A function which must return true if the passed Node is the required Node.
78996                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
78997                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78998                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78999                  */
79000                 findChildBy : function(fn, scope, deep) {
79001                     var cs = this.childNodes,
79002                         len = cs.length,
79003                         i = 0, n, res;
79004
79005                     for (; i < len; i++) {
79006                         n = cs[i];
79007                         if (fn.call(scope || n, n) === true) {
79008                             return n;
79009                         }
79010                         else if (deep) {
79011                             res = n.findChildBy(fn, scope, deep);
79012                             if (res !== null) {
79013                                 return res;
79014                             }
79015                         }
79016                     }
79017
79018                     return null;
79019                 },
79020
79021                 /**
79022                  * Returns true if this node is an ancestor (at any point) of the passed node.
79023                  * @param {Ext.data.NodeInterface} node
79024                  * @return {Boolean}
79025                  */
79026                 contains : function(node) {
79027                     return node.isAncestor(this);
79028                 },
79029
79030                 /**
79031                  * Returns true if the passed node is an ancestor (at any point) of this node.
79032                  * @param {Ext.data.NodeInterface} node
79033                  * @return {Boolean}
79034                  */
79035                 isAncestor : function(node) {
79036                     var p = this.parentNode;
79037                     while (p) {
79038                         if (p == node) {
79039                             return true;
79040                         }
79041                         p = p.parentNode;
79042                     }
79043                     return false;
79044                 },
79045
79046                 /**
79047                  * Sorts this nodes children using the supplied sort function.
79048                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
79049                  * @param {Boolean} [recursive=false] True to apply this sort recursively
79050                  * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
79051                  */
79052                 sort : function(sortFn, recursive, suppressEvent) {
79053                     var cs  = this.childNodes,
79054                         ln = cs.length,
79055                         i, n;
79056
79057                     if (ln > 0) {
79058                         Ext.Array.sort(cs, sortFn);
79059                         for (i = 0; i < ln; i++) {
79060                             n = cs[i];
79061                             n.previousSibling = cs[i-1];
79062                             n.nextSibling = cs[i+1];
79063
79064                             if (i === 0) {
79065                                 this.setFirstChild(n);
79066                                 n.updateInfo();
79067                             }
79068                             if (i == ln - 1) {
79069                                 this.setLastChild(n);
79070                                 n.updateInfo();
79071                             }
79072                             if (recursive && !n.isLeaf()) {
79073                                 n.sort(sortFn, true, true);
79074                             }
79075                         }
79076
79077                         if (suppressEvent !== true) {
79078                             this.fireEvent('sort', this, cs);
79079                         }
79080                     }
79081                 },
79082
79083                 /**
79084                  * Returns true if this node is expaned
79085                  * @return {Boolean}
79086                  */
79087                 isExpanded: function() {
79088                     return this.get('expanded');
79089                 },
79090
79091                 /**
79092                  * Returns true if this node is loaded
79093                  * @return {Boolean}
79094                  */
79095                 isLoaded: function() {
79096                     return this.get('loaded');
79097                 },
79098
79099                 /**
79100                  * Returns true if this node is loading
79101                  * @return {Boolean}
79102                  */
79103                 isLoading: function() {
79104                     return this.get('loading');
79105                 },
79106
79107                 /**
79108                  * Returns true if this node is the root node
79109                  * @return {Boolean}
79110                  */
79111                 isRoot: function() {
79112                     return !this.parentNode;
79113                 },
79114
79115                 /**
79116                  * Returns true if this node is visible
79117                  * @return {Boolean}
79118                  */
79119                 isVisible: function() {
79120                     var parent = this.parentNode;
79121                     while (parent) {
79122                         if (!parent.isExpanded()) {
79123                             return false;
79124                         }
79125                         parent = parent.parentNode;
79126                     }
79127                     return true;
79128                 },
79129
79130                 /**
79131                  * Expand this node.
79132                  * @param {Boolean} [recursive=false] True to recursively expand all the children
79133                  * @param {Function} [callback] The function to execute once the expand completes
79134                  * @param {Object} [scope] The scope to run the callback in
79135                  */
79136                 expand: function(recursive, callback, scope) {
79137                     var me = this;
79138
79139                     // all paths must call the callback (eventually) or things like
79140                     // selectPath fail
79141
79142                     // First we start by checking if this node is a parent
79143                     if (!me.isLeaf()) {
79144                         // If it's loaded, wait until it loads before proceeding
79145                         if (me.isLoading()) {
79146                             me.on('expand', function(){
79147                                 me.expand(recursive, callback, scope);
79148                             }, me, {single: true});
79149                         } else {
79150                             // Now we check if this record is already expanding or expanded
79151                             if (!me.isExpanded()) {
79152                                 // The TreeStore actually listens for the beforeexpand method and checks
79153                                 // whether we have to asynchronously load the children from the server
79154                                 // first. Thats why we pass a callback function to the event that the
79155                                 // store can call once it has loaded and parsed all the children.
79156                                 me.fireEvent('beforeexpand', me, function(){
79157                                     me.set('expanded', true);
79158                                     me.fireEvent('expand', me, me.childNodes, false);
79159
79160                                     // Call the expandChildren method if recursive was set to true
79161                                     if (recursive) {
79162                                         me.expandChildren(true, callback, scope);
79163                                     } else {
79164                                         Ext.callback(callback, scope || me, [me.childNodes]);
79165                                     }
79166                                 }, me);
79167                             } else if (recursive) {
79168                                 // If it is is already expanded but we want to recursively expand then call expandChildren
79169                                 me.expandChildren(true, callback, scope);
79170                             } else {
79171                                 Ext.callback(callback, scope || me, [me.childNodes]);
79172                             }
79173                         }
79174                     } else {
79175                         // If it's not then we fire the callback right away
79176                         Ext.callback(callback, scope || me); // leaf = no childNodes
79177                     }
79178                 },
79179
79180                 /**
79181                  * Expand all the children of this node.
79182                  * @param {Boolean} [recursive=false] True to recursively expand all the children
79183                  * @param {Function} [callback] The function to execute once all the children are expanded
79184                  * @param {Object} [scope] The scope to run the callback in
79185                  */
79186                 expandChildren: function(recursive, callback, scope) {
79187                     var me = this,
79188                         i = 0,
79189                         nodes = me.childNodes,
79190                         ln = nodes.length,
79191                         node,
79192                         expanding = 0;
79193
79194                     for (; i < ln; ++i) {
79195                         node = nodes[i];
79196                         if (!node.isLeaf() && !node.isExpanded()) {
79197                             expanding++;
79198                             nodes[i].expand(recursive, function () {
79199                                 expanding--;
79200                                 if (callback && !expanding) {
79201                                     Ext.callback(callback, scope || me, [me.childNodes]);
79202                                 }
79203                             });
79204                         }
79205                     }
79206
79207                     if (!expanding && callback) {
79208                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
79209                 },
79210
79211                 /**
79212                  * Collapse this node.
79213                  * @param {Boolean} [recursive=false] True to recursively collapse all the children
79214                  * @param {Function} [callback] The function to execute once the collapse completes
79215                  * @param {Object} [scope] The scope to run the callback in
79216                  */
79217                 collapse: function(recursive, callback, scope) {
79218                     var me = this;
79219
79220                     // First we start by checking if this node is a parent
79221                     if (!me.isLeaf()) {
79222                         // Now we check if this record is already collapsing or collapsed
79223                         if (!me.collapsing && me.isExpanded()) {
79224                             me.fireEvent('beforecollapse', me, function() {
79225                                 me.set('expanded', false);
79226                                 me.fireEvent('collapse', me, me.childNodes, false);
79227
79228                                 // Call the collapseChildren method if recursive was set to true
79229                                 if (recursive) {
79230                                     me.collapseChildren(true, callback, scope);
79231                                 }
79232                                 else {
79233                                     Ext.callback(callback, scope || me, [me.childNodes]);
79234                                 }
79235                             }, me);
79236                         }
79237                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
79238                         else if (recursive) {
79239                             me.collapseChildren(true, callback, scope);
79240                         }
79241                     }
79242                     // If it's not then we fire the callback right away
79243                     else {
79244                         Ext.callback(callback, scope || me, [me.childNodes]);
79245                     }
79246                 },
79247
79248                 /**
79249                  * Collapse all the children of this node.
79250                  * @param {Function} [recursive=false] True to recursively collapse all the children
79251                  * @param {Function} [callback] The function to execute once all the children are collapsed
79252                  * @param {Object} [scope] The scope to run the callback in
79253                  */
79254                 collapseChildren: function(recursive, callback, scope) {
79255                     var me = this,
79256                         i = 0,
79257                         nodes = me.childNodes,
79258                         ln = nodes.length,
79259                         node,
79260                         collapsing = 0;
79261
79262                     for (; i < ln; ++i) {
79263                         node = nodes[i];
79264                         if (!node.isLeaf() && node.isExpanded()) {
79265                             collapsing++;
79266                             nodes[i].collapse(recursive, function () {
79267                                 collapsing--;
79268                                 if (callback && !collapsing) {
79269                                     Ext.callback(callback, scope || me, [me.childNodes]);
79270                                 }
79271                             });
79272                         }
79273                     }
79274
79275                     if (!collapsing && callback) {
79276                         Ext.callback(callback, scope || me, [me.childNodes]);
79277                     }
79278                 }
79279             };
79280         }
79281     }
79282 });
79283 /**
79284  * @class Ext.data.NodeStore
79285  * @extends Ext.data.AbstractStore
79286  * Node Store
79287  * @ignore
79288  */
79289 Ext.define('Ext.data.NodeStore', {
79290     extend: 'Ext.data.Store',
79291     alias: 'store.node',
79292     requires: ['Ext.data.NodeInterface'],
79293     
79294     /**
79295      * @cfg {Ext.data.Model} node
79296      * The Record you want to bind this Store to. Note that
79297      * this record will be decorated with the Ext.data.NodeInterface if this is not the
79298      * case yet.
79299      */
79300     node: null,
79301     
79302     /**
79303      * @cfg {Boolean} recursive
79304      * Set this to true if you want this NodeStore to represent
79305      * all the descendents of the node in its flat data collection. This is useful for
79306      * rendering a tree structure to a DataView and is being used internally by
79307      * the TreeView. Any records that are moved, removed, inserted or appended to the
79308      * node at any depth below the node this store is bound to will be automatically
79309      * updated in this Store's internal flat data structure.
79310      */
79311     recursive: false,
79312     
79313     /** 
79314      * @cfg {Boolean} rootVisible
79315      * False to not include the root node in this Stores collection.
79316      */    
79317     rootVisible: false,
79318     
79319     constructor: function(config) {
79320         var me = this,
79321             node;
79322             
79323         config = config || {};
79324         Ext.apply(me, config);
79325         
79326         if (Ext.isDefined(me.proxy)) {
79327             Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
79328                             "decorated with the NodeInterface by setting the node config.");
79329         }
79330
79331         config.proxy = {type: 'proxy'};
79332         me.callParent([config]);
79333
79334         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
79335         
79336         node = me.node;
79337         if (node) {
79338             me.node = null;
79339             me.setNode(node);
79340         }
79341     },
79342     
79343     setNode: function(node) {
79344         var me = this;
79345         
79346         if (me.node && me.node != node) {
79347             // We want to unbind our listeners on the old node
79348             me.mun(me.node, {
79349                 expand: me.onNodeExpand,
79350                 collapse: me.onNodeCollapse,
79351                 append: me.onNodeAppend,
79352                 insert: me.onNodeInsert,
79353                 remove: me.onNodeRemove,
79354                 sort: me.onNodeSort,
79355                 scope: me
79356             });
79357             me.node = null;
79358         }
79359         
79360         if (node) {
79361             Ext.data.NodeInterface.decorate(node);
79362             me.removeAll();
79363             if (me.rootVisible) {
79364                 me.add(node);
79365             }
79366             me.mon(node, {
79367                 expand: me.onNodeExpand,
79368                 collapse: me.onNodeCollapse,
79369                 append: me.onNodeAppend,
79370                 insert: me.onNodeInsert,
79371                 remove: me.onNodeRemove,
79372                 sort: me.onNodeSort,
79373                 scope: me
79374             });
79375             me.node = node;
79376             if (node.isExpanded() && node.isLoaded()) {
79377                 me.onNodeExpand(node, node.childNodes, true);
79378             }
79379         }
79380     },
79381     
79382     onNodeSort: function(node, childNodes) {
79383         var me = this;
79384         
79385         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
79386             me.onNodeCollapse(node, childNodes, true);
79387             me.onNodeExpand(node, childNodes, true);
79388         }
79389     },
79390     
79391     onNodeExpand: function(parent, records, suppressEvent) {
79392         var me = this,
79393             insertIndex = me.indexOf(parent) + 1,
79394             ln = records ? records.length : 0,
79395             i, record;
79396             
79397         if (!me.recursive && parent !== me.node) {
79398             return;
79399         }
79400         
79401         if (!me.isVisible(parent)) {
79402             return;
79403         }
79404
79405         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
79406             return;
79407         }
79408         
79409         if (ln) {
79410             me.insert(insertIndex, records);
79411             for (i = 0; i < ln; i++) {
79412                 record = records[i];
79413                 if (record.isExpanded()) {
79414                     if (record.isLoaded()) {
79415                         // Take a shortcut                        
79416                         me.onNodeExpand(record, record.childNodes, true);
79417                     }
79418                     else {
79419                         record.set('expanded', false);
79420                         record.expand();
79421                     }
79422                 }
79423             }
79424         }
79425
79426         if (!suppressEvent) {
79427             me.fireEvent('expand', parent, records);
79428         }
79429     },
79430
79431     onNodeCollapse: function(parent, records, suppressEvent) {
79432         var me = this,
79433             ln = records.length,
79434             collapseIndex = me.indexOf(parent) + 1,
79435             i, record;
79436             
79437         if (!me.recursive && parent !== me.node) {
79438             return;
79439         }
79440         
79441         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
79442             return;
79443         }
79444
79445         for (i = 0; i < ln; i++) {
79446             record = records[i];
79447             me.remove(record);
79448             if (record.isExpanded()) {
79449                 me.onNodeCollapse(record, record.childNodes, true);
79450             }
79451         }
79452         
79453         if (!suppressEvent) {
79454             me.fireEvent('collapse', parent, records, collapseIndex);
79455         }
79456     },
79457     
79458     onNodeAppend: function(parent, node, index) {
79459         var me = this,
79460             refNode, sibling;
79461
79462         if (me.isVisible(node)) {
79463             if (index === 0) {
79464                 refNode = parent;
79465             } else {
79466                 sibling = node.previousSibling;
79467                 while (sibling.isExpanded() && sibling.lastChild) {
79468                     sibling = sibling.lastChild;
79469                 }
79470                 refNode = sibling;
79471             }
79472             me.insert(me.indexOf(refNode) + 1, node);
79473             if (!node.isLeaf() && node.isExpanded()) {
79474                 if (node.isLoaded()) {
79475                     // Take a shortcut                        
79476                     me.onNodeExpand(node, node.childNodes, true);
79477                 }
79478                 else {
79479                     node.set('expanded', false);
79480                     node.expand();
79481                 }
79482             }
79483         } 
79484     },
79485     
79486     onNodeInsert: function(parent, node, refNode) {
79487         var me = this,
79488             index = this.indexOf(refNode);
79489             
79490         if (index != -1 && me.isVisible(node)) {
79491             me.insert(index, node);
79492             if (!node.isLeaf() && node.isExpanded()) {
79493                 if (node.isLoaded()) {
79494                     // Take a shortcut                        
79495                     me.onNodeExpand(node, node.childNodes, true);
79496                 }
79497                 else {
79498                     node.set('expanded', false);
79499                     node.expand();
79500                 }
79501             }
79502         }
79503     },
79504     
79505     onNodeRemove: function(parent, node, index) {
79506         var me = this;
79507         if (me.indexOf(node) != -1) {
79508             if (!node.isLeaf() && node.isExpanded()) {
79509                 me.onNodeCollapse(node, node.childNodes, true);
79510             }            
79511             me.remove(node);
79512         }
79513     },
79514     
79515     isVisible: function(node) {
79516         var parent = node.parentNode;
79517         while (parent) {
79518             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
79519                 return true;
79520             }
79521             
79522             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
79523                 return false;
79524             }
79525             
79526             parent = parent.parentNode;
79527         }
79528         return true;
79529     }
79530 });
79531 /**
79532  * @author Ed Spencer
79533  * 
79534  * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
79535  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
79536  * it does not contain any actual logic or perform the request itself.
79537  */
79538 Ext.define('Ext.data.Request', {
79539     /**
79540      * @cfg {String} action
79541      * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
79542      */
79543     action: undefined,
79544     
79545     /**
79546      * @cfg {Object} params
79547      * HTTP request params. The Proxy and its Writer have access to and can modify this object.
79548      */
79549     params: undefined,
79550     
79551     /**
79552      * @cfg {String} method
79553      * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
79554      */
79555     method: 'GET',
79556     
79557     /**
79558      * @cfg {String} url
79559      * The url to access on this Request
79560      */
79561     url: undefined,
79562
79563     /**
79564      * Creates the Request object.
79565      * @param {Object} [config] Config object.
79566      */
79567     constructor: function(config) {
79568         Ext.apply(this, config);
79569     }
79570 });
79571 /**
79572  * @author Don Griffin
79573  *
79574  * This class is a sequential id generator. A simple use of this class would be like so:
79575  *
79576  *     Ext.define('MyApp.data.MyModel', {
79577  *         extend: 'Ext.data.Model',
79578  *         idgen: 'sequential'
79579  *     });
79580  *     // assign id's of 1, 2, 3, etc.
79581  *
79582  * An example of a configured generator would be:
79583  *
79584  *     Ext.define('MyApp.data.MyModel', {
79585  *         extend: 'Ext.data.Model',
79586  *         idgen: {
79587  *             type: 'sequential',
79588  *             prefix: 'ID_',
79589  *             seed: 1000
79590  *         }
79591  *     });
79592  *     // assign id's of ID_1000, ID_1001, ID_1002, etc.
79593  *
79594  */
79595 Ext.define('Ext.data.SequentialIdGenerator', {
79596     extend: 'Ext.data.IdGenerator',
79597     alias: 'idgen.sequential',
79598
79599     constructor: function() {
79600         var me = this;
79601
79602         me.callParent(arguments);
79603
79604         me.parts = [ me.prefix, ''];
79605     },
79606
79607     /**
79608      * @cfg {String} prefix
79609      * The string to place in front of the sequential number for each generated id. The
79610      * default is blank.
79611      */
79612     prefix: '',
79613
79614     /**
79615      * @cfg {Number} seed
79616      * The number at which to start generating sequential id's. The default is 1.
79617      */
79618     seed: 1,
79619
79620     /**
79621      * Generates and returns the next id.
79622      * @return {String} The next id.
79623      */
79624     generate: function () {
79625         var me = this,
79626             parts = me.parts;
79627
79628         parts[1] = me.seed++;
79629         return parts.join('');
79630     }
79631 });
79632
79633 /**
79634  * @class Ext.data.Tree
79635  *
79636  * This class is used as a container for a series of nodes. The nodes themselves maintain
79637  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
79638  * to retrieve a node by its identifier: {@link #getNodeById}.
79639  *
79640  * The tree also relays events from any of it's child nodes, allowing them to be handled in a
79641  * centralized fashion. In general this class is not used directly, rather used internally
79642  * by other parts of the framework.
79643  *
79644  */
79645 Ext.define('Ext.data.Tree', {
79646     alias: 'data.tree',
79647
79648     mixins: {
79649         observable: "Ext.util.Observable"
79650     },
79651
79652     /**
79653      * @property {Ext.data.NodeInterface}
79654      * The root node for this tree
79655      */
79656     root: null,
79657
79658     /**
79659      * Creates new Tree object.
79660      * @param {Ext.data.NodeInterface} root (optional) The root node
79661      */
79662     constructor: function(root) {
79663         var me = this;
79664
79665         
79666
79667         me.mixins.observable.constructor.call(me);
79668
79669         if (root) {
79670             me.setRootNode(root);
79671         }
79672     },
79673
79674     /**
79675      * Returns the root node for this tree.
79676      * @return {Ext.data.NodeInterface}
79677      */
79678     getRootNode : function() {
79679         return this.root;
79680     },
79681
79682     /**
79683      * Sets the root node for this tree.
79684      * @param {Ext.data.NodeInterface} node
79685      * @return {Ext.data.NodeInterface} The root node
79686      */
79687     setRootNode : function(node) {
79688         var me = this;
79689
79690         me.root = node;
79691         Ext.data.NodeInterface.decorate(node);
79692
79693         if (me.fireEvent('beforeappend', null, node) !== false) {
79694             node.set('root', true);
79695             node.updateInfo();
79696
79697             me.relayEvents(node, [
79698                 /**
79699                  * @event append
79700                  * @alias Ext.data.NodeInterface#append
79701                  */
79702                 "append",
79703
79704                 /**
79705                  * @event remove
79706                  * @alias Ext.data.NodeInterface#remove
79707                  */
79708                 "remove",
79709
79710                 /**
79711                  * @event move
79712                  * @alias Ext.data.NodeInterface#move
79713                  */
79714                 "move",
79715
79716                 /**
79717                  * @event insert
79718                  * @alias Ext.data.NodeInterface#insert
79719                  */
79720                 "insert",
79721
79722                 /**
79723                  * @event beforeappend
79724                  * @alias Ext.data.NodeInterface#beforeappend
79725                  */
79726                 "beforeappend",
79727
79728                 /**
79729                  * @event beforeremove
79730                  * @alias Ext.data.NodeInterface#beforeremove
79731                  */
79732                 "beforeremove",
79733
79734                 /**
79735                  * @event beforemove
79736                  * @alias Ext.data.NodeInterface#beforemove
79737                  */
79738                 "beforemove",
79739
79740                 /**
79741                  * @event beforeinsert
79742                  * @alias Ext.data.NodeInterface#beforeinsert
79743                  */
79744                 "beforeinsert",
79745
79746                  /**
79747                   * @event expand
79748                   * @alias Ext.data.NodeInterface#expand
79749                   */
79750                  "expand",
79751
79752                  /**
79753                   * @event collapse
79754                   * @alias Ext.data.NodeInterface#collapse
79755                   */
79756                  "collapse",
79757
79758                  /**
79759                   * @event beforeexpand
79760                   * @alias Ext.data.NodeInterface#beforeexpand
79761                   */
79762                  "beforeexpand",
79763
79764                  /**
79765                   * @event beforecollapse
79766                   * @alias Ext.data.NodeInterface#beforecollapse
79767                   */
79768                  "beforecollapse" ,
79769
79770                  /**
79771                   * @event rootchange
79772                   * Fires whenever the root node is changed in the tree.
79773                   * @param {Ext.data.Model} root The new root
79774                   */
79775                  "rootchange"
79776             ]);
79777
79778             node.on({
79779                 scope: me,
79780                 insert: me.onNodeInsert,
79781                 append: me.onNodeAppend,
79782                 remove: me.onNodeRemove
79783             });
79784
79785             me.nodeHash = {};
79786             me.registerNode(node);
79787             me.fireEvent('append', null, node);
79788             me.fireEvent('rootchange', node);
79789         }
79790
79791         return node;
79792     },
79793
79794     /**
79795      * Flattens all the nodes in the tree into an array.
79796      * @private
79797      * @return {Ext.data.NodeInterface[]} The flattened nodes.
79798      */
79799     flatten: function(){
79800         var nodes = [],
79801             hash = this.nodeHash,
79802             key;
79803
79804         for (key in hash) {
79805             if (hash.hasOwnProperty(key)) {
79806                 nodes.push(hash[key]);
79807             }
79808         }
79809         return nodes;
79810     },
79811
79812     /**
79813      * Fired when a node is inserted into the root or one of it's children
79814      * @private
79815      * @param {Ext.data.NodeInterface} parent The parent node
79816      * @param {Ext.data.NodeInterface} node The inserted node
79817      */
79818     onNodeInsert: function(parent, node) {
79819         this.registerNode(node, true);
79820     },
79821
79822     /**
79823      * Fired when a node is appended into the root or one of it's children
79824      * @private
79825      * @param {Ext.data.NodeInterface} parent The parent node
79826      * @param {Ext.data.NodeInterface} node The appended node
79827      */
79828     onNodeAppend: function(parent, node) {
79829         this.registerNode(node, true);
79830     },
79831
79832     /**
79833      * Fired when a node is removed from the root or one of it's children
79834      * @private
79835      * @param {Ext.data.NodeInterface} parent The parent node
79836      * @param {Ext.data.NodeInterface} node The removed node
79837      */
79838     onNodeRemove: function(parent, node) {
79839         this.unregisterNode(node, true);
79840     },
79841
79842     /**
79843      * Gets a node in this tree by its id.
79844      * @param {String} id
79845      * @return {Ext.data.NodeInterface} The match node.
79846      */
79847     getNodeById : function(id) {
79848         return this.nodeHash[id];
79849     },
79850
79851     /**
79852      * Registers a node with the tree
79853      * @private
79854      * @param {Ext.data.NodeInterface} The node to register
79855      * @param {Boolean} [includeChildren] True to unregister any child nodes
79856      */
79857     registerNode : function(node, includeChildren) {
79858         this.nodeHash[node.getId() || node.internalId] = node;
79859         if (includeChildren === true) {
79860             node.eachChild(function(child){
79861                 this.registerNode(child, true);
79862             }, this);
79863         }
79864     },
79865
79866     /**
79867      * Unregisters a node with the tree
79868      * @private
79869      * @param {Ext.data.NodeInterface} The node to unregister
79870      * @param {Boolean} [includeChildren] True to unregister any child nodes
79871      */
79872     unregisterNode : function(node, includeChildren) {
79873         delete this.nodeHash[node.getId() || node.internalId];
79874         if (includeChildren === true) {
79875             node.eachChild(function(child){
79876                 this.unregisterNode(child, true);
79877             }, this);
79878         }
79879     },
79880
79881     /**
79882      * Sorts this tree
79883      * @private
79884      * @param {Function} sorterFn The function to use for sorting
79885      * @param {Boolean} recursive True to perform recursive sorting
79886      */
79887     sort: function(sorterFn, recursive) {
79888         this.getRootNode().sort(sorterFn, recursive);
79889     },
79890
79891      /**
79892      * Filters this tree
79893      * @private
79894      * @param {Function} sorterFn The function to use for filtering
79895      * @param {Boolean} recursive True to perform recursive filtering
79896      */
79897     filter: function(filters, recursive) {
79898         this.getRootNode().filter(filters, recursive);
79899     }
79900 });
79901 /**
79902  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
79903  * It provides convenience methods for loading nodes, as well as the ability to use
79904  * the hierarchical tree structure combined with a store. This class is generally used
79905  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
79906  * the Tree for convenience.
79907  *
79908  * # Using Models
79909  *
79910  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
79911  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
79912  * in the {@link Ext.data.NodeInterface} documentation.
79913  *
79914  * # Reading Nested Data
79915  *
79916  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
79917  * so the reader can find nested data for each node. If a root is not specified, it will default to
79918  * 'children'.
79919  */
79920 Ext.define('Ext.data.TreeStore', {
79921     extend: 'Ext.data.AbstractStore',
79922     alias: 'store.tree',
79923     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
79924
79925     /**
79926      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
79927      * The root node for this store. For example:
79928      *
79929      *     root: {
79930      *         expanded: true,
79931      *         text: "My Root",
79932      *         children: [
79933      *             { text: "Child 1", leaf: true },
79934      *             { text: "Child 2", expanded: true, children: [
79935      *                 { text: "GrandChild", leaf: true }
79936      *             ] }
79937      *         ]
79938      *     }
79939      *
79940      * Setting the `root` config option is the same as calling {@link #setRootNode}.
79941      */
79942
79943     /**
79944      * @cfg {Boolean} clearOnLoad
79945      * Remove previously existing child nodes before loading. Default to true.
79946      */
79947     clearOnLoad : true,
79948
79949     /**
79950      * @cfg {String} nodeParam
79951      * The name of the parameter sent to the server which contains the identifier of the node.
79952      * Defaults to 'node'.
79953      */
79954     nodeParam: 'node',
79955
79956     /**
79957      * @cfg {String} defaultRootId
79958      * The default root id. Defaults to 'root'
79959      */
79960     defaultRootId: 'root',
79961
79962     /**
79963      * @cfg {String} defaultRootProperty
79964      * The root property to specify on the reader if one is not explicitly defined.
79965      */
79966     defaultRootProperty: 'children',
79967
79968     /**
79969      * @cfg {Boolean} folderSort
79970      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
79971      */
79972     folderSort: false,
79973
79974     constructor: function(config) {
79975         var me = this,
79976             root,
79977             fields;
79978
79979         config = Ext.apply({}, config);
79980
79981         /**
79982          * If we have no fields declare for the store, add some defaults.
79983          * These will be ignored if a model is explicitly specified.
79984          */
79985         fields = config.fields || me.fields;
79986         if (!fields) {
79987             config.fields = [{name: 'text', type: 'string'}];
79988         }
79989
79990         me.callParent([config]);
79991
79992         // We create our data tree.
79993         me.tree = Ext.create('Ext.data.Tree');
79994
79995         me.relayEvents(me.tree, [
79996             /**
79997              * @event append
79998              * @alias Ext.data.Tree#append
79999              */
80000             "append",
80001
80002             /**
80003              * @event remove
80004              * @alias Ext.data.Tree#remove
80005              */
80006             "remove",
80007
80008             /**
80009              * @event move
80010              * @alias Ext.data.Tree#move
80011              */
80012             "move",
80013
80014             /**
80015              * @event insert
80016              * @alias Ext.data.Tree#insert
80017              */
80018             "insert",
80019
80020             /**
80021              * @event beforeappend
80022              * @alias Ext.data.Tree#beforeappend
80023              */
80024             "beforeappend",
80025
80026             /**
80027              * @event beforeremove
80028              * @alias Ext.data.Tree#beforeremove
80029              */
80030             "beforeremove",
80031
80032             /**
80033              * @event beforemove
80034              * @alias Ext.data.Tree#beforemove
80035              */
80036             "beforemove",
80037
80038             /**
80039              * @event beforeinsert
80040              * @alias Ext.data.Tree#beforeinsert
80041              */
80042             "beforeinsert",
80043
80044              /**
80045               * @event expand
80046               * @alias Ext.data.Tree#expand
80047               */
80048              "expand",
80049
80050              /**
80051               * @event collapse
80052               * @alias Ext.data.Tree#collapse
80053               */
80054              "collapse",
80055
80056              /**
80057               * @event beforeexpand
80058               * @alias Ext.data.Tree#beforeexpand
80059               */
80060              "beforeexpand",
80061
80062              /**
80063               * @event beforecollapse
80064               * @alias Ext.data.Tree#beforecollapse
80065               */
80066              "beforecollapse",
80067
80068              /**
80069               * @event rootchange
80070               * @alias Ext.data.Tree#rootchange
80071               */
80072              "rootchange"
80073         ]);
80074
80075         me.tree.on({
80076             scope: me,
80077             remove: me.onNodeRemove,
80078             // this event must follow the relay to beforeitemexpand to allow users to
80079             // cancel the expand:
80080             beforeexpand: me.onBeforeNodeExpand,
80081             beforecollapse: me.onBeforeNodeCollapse,
80082             append: me.onNodeAdded,
80083             insert: me.onNodeAdded
80084         });
80085
80086         me.onBeforeSort();
80087
80088         root = me.root;
80089         if (root) {
80090             delete me.root;
80091             me.setRootNode(root);
80092         }
80093
80094         me.addEvents(
80095             /**
80096              * @event sort
80097              * Fires when this TreeStore is sorted.
80098              * @param {Ext.data.NodeInterface} node The node that is sorted.
80099              */
80100             'sort'
80101         );
80102
80103         if (Ext.isDefined(me.nodeParameter)) {
80104             if (Ext.isDefined(Ext.global.console)) {
80105                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
80106             }
80107             me.nodeParam = me.nodeParameter;
80108             delete me.nodeParameter;
80109         }
80110     },
80111
80112     // inherit docs
80113     setProxy: function(proxy) {
80114         var reader,
80115             needsRoot;
80116
80117         if (proxy instanceof Ext.data.proxy.Proxy) {
80118             // proxy instance, check if a root was set
80119             needsRoot = Ext.isEmpty(proxy.getReader().root);
80120         } else if (Ext.isString(proxy)) {
80121             // string type, means a reader can't be set
80122             needsRoot = true;
80123         } else {
80124             // object, check if a reader and a root were specified.
80125             reader = proxy.reader;
80126             needsRoot = !(reader && !Ext.isEmpty(reader.root));
80127         }
80128         proxy = this.callParent(arguments);
80129         if (needsRoot) {
80130             reader = proxy.getReader();
80131             reader.root = this.defaultRootProperty;
80132             // force rebuild
80133             reader.buildExtractors(true);
80134         }
80135     },
80136
80137     // inherit docs
80138     onBeforeSort: function() {
80139         if (this.folderSort) {
80140             this.sort({
80141                 property: 'leaf',
80142                 direction: 'ASC'
80143             }, 'prepend', false);
80144         }
80145     },
80146
80147     /**
80148      * Called before a node is expanded.
80149      * @private
80150      * @param {Ext.data.NodeInterface} node The node being expanded.
80151      * @param {Function} callback The function to run after the expand finishes
80152      * @param {Object} scope The scope in which to run the callback function
80153      */
80154     onBeforeNodeExpand: function(node, callback, scope) {
80155         if (node.isLoaded()) {
80156             Ext.callback(callback, scope || node, [node.childNodes]);
80157         }
80158         else if (node.isLoading()) {
80159             this.on('load', function() {
80160                 Ext.callback(callback, scope || node, [node.childNodes]);
80161             }, this, {single: true});
80162         }
80163         else {
80164             this.read({
80165                 node: node,
80166                 callback: function() {
80167                     Ext.callback(callback, scope || node, [node.childNodes]);
80168                 }
80169             });
80170         }
80171     },
80172
80173     //inherit docs
80174     getNewRecords: function() {
80175         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
80176     },
80177
80178     //inherit docs
80179     getUpdatedRecords: function() {
80180         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
80181     },
80182
80183     /**
80184      * Called before a node is collapsed.
80185      * @private
80186      * @param {Ext.data.NodeInterface} node The node being collapsed.
80187      * @param {Function} callback The function to run after the collapse finishes
80188      * @param {Object} scope The scope in which to run the callback function
80189      */
80190     onBeforeNodeCollapse: function(node, callback, scope) {
80191         callback.call(scope || node, node.childNodes);
80192     },
80193
80194     onNodeRemove: function(parent, node) {
80195         var removed = this.removed;
80196
80197         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
80198             removed.push(node);
80199         }
80200     },
80201
80202     onNodeAdded: function(parent, node) {
80203         var proxy = this.getProxy(),
80204             reader = proxy.getReader(),
80205             data = node.raw || node.data,
80206             dataRoot, children;
80207
80208         Ext.Array.remove(this.removed, node);
80209
80210         if (!node.isLeaf() && !node.isLoaded()) {
80211             dataRoot = reader.getRoot(data);
80212             if (dataRoot) {
80213                 this.fillNode(node, reader.extractData(dataRoot));
80214                 delete data[reader.root];
80215             }
80216         }
80217     },
80218
80219     /**
80220      * Sets the root node for this store.  See also the {@link #root} config option.
80221      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
80222      * @return {Ext.data.NodeInterface} The new root
80223      */
80224     setRootNode: function(root) {
80225         var me = this;
80226
80227         root = root || {};
80228         if (!root.isNode) {
80229             // create a default rootNode and create internal data struct.
80230             Ext.applyIf(root, {
80231                 id: me.defaultRootId,
80232                 text: 'Root',
80233                 allowDrag: false
80234             });
80235             root = Ext.ModelManager.create(root, me.model);
80236         }
80237         Ext.data.NodeInterface.decorate(root);
80238
80239         // Because we have decorated the model with new fields,
80240         // we need to build new extactor functions on the reader.
80241         me.getProxy().getReader().buildExtractors(true);
80242
80243         // When we add the root to the tree, it will automaticaly get the NodeInterface
80244         me.tree.setRootNode(root);
80245
80246         // If the user has set expanded: true on the root, we want to call the expand function
80247         if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
80248             me.load({
80249                 node: root
80250             });
80251         }
80252
80253         return root;
80254     },
80255
80256     /**
80257      * Returns the root node for this tree.
80258      * @return {Ext.data.NodeInterface}
80259      */
80260     getRootNode: function() {
80261         return this.tree.getRootNode();
80262     },
80263
80264     /**
80265      * Returns the record node by id
80266      * @return {Ext.data.NodeInterface}
80267      */
80268     getNodeById: function(id) {
80269         return this.tree.getNodeById(id);
80270     },
80271
80272     /**
80273      * Loads the Store using its configured {@link #proxy}.
80274      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
80275      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
80276      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
80277      * default to the root node.
80278      */
80279     load: function(options) {
80280         options = options || {};
80281         options.params = options.params || {};
80282
80283         var me = this,
80284             node = options.node || me.tree.getRootNode(),
80285             root;
80286
80287         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
80288         // create one for them.
80289         if (!node) {
80290             node = me.setRootNode({
80291                 expanded: true
80292             });
80293         }
80294
80295         if (me.clearOnLoad) {
80296             node.removeAll(true);
80297         }
80298
80299         Ext.applyIf(options, {
80300             node: node
80301         });
80302         options.params[me.nodeParam] = node ? node.getId() : 'root';
80303
80304         if (node) {
80305             node.set('loading', true);
80306         }
80307
80308         return me.callParent([options]);
80309     },
80310
80311
80312     /**
80313      * Fills a node with a series of child records.
80314      * @private
80315      * @param {Ext.data.NodeInterface} node The node to fill
80316      * @param {Ext.data.Model[]} records The records to add
80317      */
80318     fillNode: function(node, records) {
80319         var me = this,
80320             ln = records ? records.length : 0,
80321             i = 0, sortCollection;
80322
80323         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
80324             sortCollection = Ext.create('Ext.util.MixedCollection');
80325             sortCollection.addAll(records);
80326             sortCollection.sort(me.sorters.items);
80327             records = sortCollection.items;
80328         }
80329
80330         node.set('loaded', true);
80331         for (; i < ln; i++) {
80332             node.appendChild(records[i], undefined, true);
80333         }
80334
80335         return records;
80336     },
80337
80338     // inherit docs
80339     onProxyLoad: function(operation) {
80340         var me = this,
80341             successful = operation.wasSuccessful(),
80342             records = operation.getRecords(),
80343             node = operation.node;
80344
80345         me.loading = false;
80346         node.set('loading', false);
80347         if (successful) {
80348             records = me.fillNode(node, records);
80349         }
80350         // The load event has an extra node parameter
80351         // (differing from the load event described in AbstractStore)
80352         /**
80353          * @event load
80354          * Fires whenever the store reads data from a remote data source.
80355          * @param {Ext.data.TreeStore} this
80356          * @param {Ext.data.NodeInterface} node The node that was loaded.
80357          * @param {Ext.data.Model[]} records An array of records.
80358          * @param {Boolean} successful True if the operation was successful.
80359          */
80360         // deprecate read?
80361         me.fireEvent('read', me, operation.node, records, successful);
80362         me.fireEvent('load', me, operation.node, records, successful);
80363         //this is a callback that would have been passed to the 'read' function and is optional
80364         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
80365     },
80366
80367     /**
80368      * Creates any new records when a write is returned from the server.
80369      * @private
80370      * @param {Ext.data.Model[]} records The array of new records
80371      * @param {Ext.data.Operation} operation The operation that just completed
80372      * @param {Boolean} success True if the operation was successful
80373      */
80374     onCreateRecords: function(records, operation, success) {
80375         if (success) {
80376             var i = 0,
80377                 length = records.length,
80378                 originalRecords = operation.records,
80379                 parentNode,
80380                 record,
80381                 original,
80382                 index;
80383
80384             /*
80385              * Loop over each record returned from the server. Assume they are
80386              * returned in order of how they were sent. If we find a matching
80387              * record, replace it with the newly created one.
80388              */
80389             for (; i < length; ++i) {
80390                 record = records[i];
80391                 original = originalRecords[i];
80392                 if (original) {
80393                     parentNode = original.parentNode;
80394                     if (parentNode) {
80395                         // prevent being added to the removed cache
80396                         original.isReplace = true;
80397                         parentNode.replaceChild(record, original);
80398                         delete original.isReplace;
80399                     }
80400                     record.phantom = false;
80401                 }
80402             }
80403         }
80404     },
80405
80406     /**
80407      * Updates any records when a write is returned from the server.
80408      * @private
80409      * @param {Ext.data.Model[]} records The array of updated records
80410      * @param {Ext.data.Operation} operation The operation that just completed
80411      * @param {Boolean} success True if the operation was successful
80412      */
80413     onUpdateRecords: function(records, operation, success){
80414         if (success) {
80415             var me = this,
80416                 i = 0,
80417                 length = records.length,
80418                 data = me.data,
80419                 original,
80420                 parentNode,
80421                 record;
80422
80423             for (; i < length; ++i) {
80424                 record = records[i];
80425                 original = me.tree.getNodeById(record.getId());
80426                 parentNode = original.parentNode;
80427                 if (parentNode) {
80428                     // prevent being added to the removed cache
80429                     original.isReplace = true;
80430                     parentNode.replaceChild(record, original);
80431                     original.isReplace = false;
80432                 }
80433             }
80434         }
80435     },
80436
80437     /**
80438      * Removes any records when a write is returned from the server.
80439      * @private
80440      * @param {Ext.data.Model[]} records The array of removed records
80441      * @param {Ext.data.Operation} operation The operation that just completed
80442      * @param {Boolean} success True if the operation was successful
80443      */
80444     onDestroyRecords: function(records, operation, success){
80445         if (success) {
80446             this.removed = [];
80447         }
80448     },
80449
80450     // inherit docs
80451     removeAll: function() {
80452         this.getRootNode().destroy(true);
80453         this.fireEvent('clear', this);
80454     },
80455
80456     // inherit docs
80457     doSort: function(sorterFn) {
80458         var me = this;
80459         if (me.remoteSort) {
80460             //the load function will pick up the new sorters and request the sorted data from the proxy
80461             me.load();
80462         } else {
80463             me.tree.sort(sorterFn, true);
80464             me.fireEvent('datachanged', me);
80465         }
80466         me.fireEvent('sort', me);
80467     }
80468 });
80469
80470 /**
80471  * @extend Ext.data.IdGenerator
80472  * @author Don Griffin
80473  *
80474  * This class generates UUID's according to RFC 4122. This class has a default id property.
80475  * This means that a single instance is shared unless the id property is overridden. Thus,
80476  * two {@link Ext.data.Model} instances configured like the following share one generator:
80477  *
80478  *     Ext.define('MyApp.data.MyModelX', {
80479  *         extend: 'Ext.data.Model',
80480  *         idgen: 'uuid'
80481  *     });
80482  *
80483  *     Ext.define('MyApp.data.MyModelY', {
80484  *         extend: 'Ext.data.Model',
80485  *         idgen: 'uuid'
80486  *     });
80487  *
80488  * This allows all models using this class to share a commonly configured instance.
80489  *
80490  * # Using Version 1 ("Sequential") UUID's
80491  *
80492  * If a server can provide a proper timestamp and a "cryptographic quality random number"
80493  * (as described in RFC 4122), the shared instance can be configured as follows:
80494  *
80495  *     Ext.data.IdGenerator.get('uuid').reconfigure({
80496  *         version: 1,
80497  *         clockSeq: clock, // 14 random bits
80498  *         salt: salt,      // 48 secure random bits (the Node field)
80499  *         timestamp: ts    // timestamp per Section 4.1.4
80500  *     });
80501  *
80502  *     // or these values can be split into 32-bit chunks:
80503  *
80504  *     Ext.data.IdGenerator.get('uuid').reconfigure({
80505  *         version: 1,
80506  *         clockSeq: clock,
80507  *         salt: { lo: saltLow32, hi: saltHigh32 },
80508  *         timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
80509  *     });
80510  *
80511  * This approach improves the generator's uniqueness by providing a valid timestamp and
80512  * higher quality random data. Version 1 UUID's should not be used unless this information
80513  * can be provided by a server and care should be taken to avoid caching of this data.
80514  *
80515  * See http://www.ietf.org/rfc/rfc4122.txt for details.
80516  */
80517 Ext.define('Ext.data.UuidGenerator', function () {
80518     var twoPow14 = Math.pow(2, 14),
80519         twoPow16 = Math.pow(2, 16),
80520         twoPow28 = Math.pow(2, 28),
80521         twoPow32 = Math.pow(2, 32);
80522
80523     function toHex (value, length) {
80524         var ret = value.toString(16);
80525         if (ret.length > length) {
80526             ret = ret.substring(ret.length - length); // right-most digits
80527         } else if (ret.length < length) {
80528             ret = Ext.String.leftPad(ret, length, '0');
80529         }
80530         return ret;
80531     }
80532
80533     function rand (lo, hi) {
80534         var v = Math.random() * (hi - lo + 1);
80535         return Math.floor(v) + lo;
80536     }
80537
80538     function split (bignum) {
80539         if (typeof(bignum) == 'number') {
80540             var hi = Math.floor(bignum / twoPow32);
80541             return {
80542                 lo: Math.floor(bignum - hi * twoPow32),
80543                 hi: hi
80544             };
80545         }
80546         return bignum;
80547     }
80548
80549     return {
80550         extend: 'Ext.data.IdGenerator',
80551
80552         alias: 'idgen.uuid',
80553
80554         id: 'uuid', // shared by default
80555
80556         /**
80557          * @property {Number/Object} salt
80558          * When created, this value is a 48-bit number. For computation, this value is split
80559          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
80560          */
80561
80562         /**
80563          * @property {Number/Object} timestamp
80564          * When created, this value is a 60-bit number. For computation, this value is split
80565          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
80566          */
80567
80568         /**
80569          * @cfg {Number} version
80570          * The Version of UUID. Supported values are:
80571          *
80572          *  * 1 : Time-based, "sequential" UUID.
80573          *  * 4 : Pseudo-random UUID.
80574          *
80575          * The default is 4.
80576          */
80577         version: 4,
80578
80579         constructor: function() {
80580             var me = this;
80581
80582             me.callParent(arguments);
80583
80584             me.parts = [];
80585             me.init();
80586         },
80587
80588         generate: function () {
80589             var me = this,
80590                 parts = me.parts,
80591                 ts = me.timestamp;
80592
80593             /*
80594                The magic decoder ring (derived from RFC 4122 Section 4.2.2):
80595
80596                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80597                |                          time_low                             |
80598                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80599                |           time_mid            |  ver  |        time_hi        |
80600                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80601                |res|  clock_hi |   clock_low   |    salt 0   |M|     salt 1    |
80602                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80603                |                         salt (2-5)                            |
80604                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80605
80606                          time_mid      clock_hi (low 6 bits)
80607                 time_low     | time_hi |clock_lo
80608                     |        |     |   || salt[0]
80609                     |        |     |   ||   | salt[1..5]
80610                     v        v     v   vv   v v
80611                     0badf00d-aced-1def-b123-dfad0badbeef
80612                                   ^    ^     ^
80613                             version    |     multicast (low bit)
80614                                        |
80615                                     reserved (upper 2 bits)
80616             */
80617             parts[0] = toHex(ts.lo, 8);
80618             parts[1] = toHex(ts.hi & 0xFFFF, 4);
80619             parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
80620             parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
80621                        toHex(me.clockSeq & 0xFF, 2);
80622             parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
80623
80624             if (me.version == 4) {
80625                 me.init(); // just regenerate all the random values...
80626             } else {
80627                 // sequentially increment the timestamp...
80628                 ++ts.lo;
80629                 if (ts.lo >= twoPow32) { // if (overflow)
80630                     ts.lo = 0;
80631                     ++ts.hi;
80632                 }
80633             }
80634
80635             return parts.join('-').toLowerCase();
80636         },
80637
80638         getRecId: function (rec) {
80639             return rec.getId();
80640         },
80641
80642         /**
80643          * @private
80644          */
80645         init: function () {
80646             var me = this,
80647                 salt, time;
80648
80649             if (me.version == 4) {
80650                 // See RFC 4122 (Secion 4.4)
80651                 //   o  If the state was unavailable (e.g., non-existent or corrupted),
80652                 //      or the saved node ID is different than the current node ID,
80653                 //      generate a random clock sequence value.
80654                 me.clockSeq = rand(0, twoPow14-1);
80655
80656                 // we run this on every id generation...
80657                 salt = me.salt || (me.salt = {});
80658                 time = me.timestamp || (me.timestamp = {});
80659
80660                 // See RFC 4122 (Secion 4.4)
80661                 salt.lo = rand(0, twoPow32-1);
80662                 salt.hi = rand(0, twoPow16-1);
80663                 time.lo = rand(0, twoPow32-1);
80664                 time.hi = rand(0, twoPow28-1);
80665             } else {
80666                 // this is run only once per-instance
80667                 me.salt = split(me.salt);
80668                 me.timestamp = split(me.timestamp);
80669
80670                 // Set multicast bit: "the least significant bit of the first octet of the
80671                 // node ID" (nodeId = salt for this implementation):
80672                 me.salt.hi |= 0x100;
80673             }
80674         },
80675
80676         /**
80677          * Reconfigures this generator given new config properties.
80678          */
80679         reconfigure: function (config) {
80680             Ext.apply(this, config);
80681             this.init();
80682         }
80683     };
80684 }());
80685
80686 /**
80687  * @author Ed Spencer
80688  * @class Ext.data.XmlStore
80689  * @extends Ext.data.Store
80690  * @private
80691  * @ignore
80692  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
80693  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
80694  * <p>A store configuration would be something like:<pre><code>
80695 var store = new Ext.data.XmlStore({
80696     // store configs
80697     autoDestroy: true,
80698     storeId: 'myStore',
80699     url: 'sheldon.xml', // automatically configures a HttpProxy
80700     // reader configs
80701     record: 'Item', // records will have an "Item" tag
80702     idPath: 'ASIN',
80703     totalRecords: '@TotalResults'
80704     fields: [
80705         // set up the fields mapping into the xml doc
80706         // The first needs mapping, the others are very basic
80707         {name: 'Author', mapping: 'ItemAttributes > Author'},
80708         'Title', 'Manufacturer', 'ProductGroup'
80709     ]
80710 });
80711  * </code></pre></p>
80712  * <p>This store is configured to consume a returned object of the form:<pre><code>
80713 &#60?xml version="1.0" encoding="UTF-8"?>
80714 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
80715     &#60Items>
80716         &#60Request>
80717             &#60IsValid>True&#60/IsValid>
80718             &#60ItemSearchRequest>
80719                 &#60Author>Sidney Sheldon&#60/Author>
80720                 &#60SearchIndex>Books&#60/SearchIndex>
80721             &#60/ItemSearchRequest>
80722         &#60/Request>
80723         &#60TotalResults>203&#60/TotalResults>
80724         &#60TotalPages>21&#60/TotalPages>
80725         &#60Item>
80726             &#60ASIN>0446355453&#60/ASIN>
80727             &#60DetailPageURL>
80728                 http://www.amazon.com/
80729             &#60/DetailPageURL>
80730             &#60ItemAttributes>
80731                 &#60Author>Sidney Sheldon&#60/Author>
80732                 &#60Manufacturer>Warner Books&#60/Manufacturer>
80733                 &#60ProductGroup>Book&#60/ProductGroup>
80734                 &#60Title>Master of the Game&#60/Title>
80735             &#60/ItemAttributes>
80736         &#60/Item>
80737     &#60/Items>
80738 &#60/ItemSearchResponse>
80739  * </code></pre>
80740  * An object literal of this form could also be used as the {@link #data} config option.</p>
80741  * <p><b>Note:</b> This class accepts all of the configuration options of
80742  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
80743  * @xtype xmlstore
80744  */
80745 Ext.define('Ext.data.XmlStore', {
80746     extend: 'Ext.data.Store',
80747     alternateClassName: 'Ext.data.XmlStore',
80748     alias: 'store.xml',
80749
80750     /**
80751      * @cfg {Ext.data.DataReader} reader @hide
80752      */
80753     constructor: function(config){
80754         config = config || {};
80755         config = config || {};
80756
80757         Ext.applyIf(config, {
80758             proxy: {
80759                 type: 'ajax',
80760                 reader: 'xml',
80761                 writer: 'xml'
80762             }
80763         });
80764
80765         this.callParent([config]);
80766     }
80767 });
80768
80769 /**
80770  * @author Ed Spencer
80771  *
80772  * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
80773  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
80774  * @private
80775  */
80776 Ext.define('Ext.data.proxy.Client', {
80777     extend: 'Ext.data.proxy.Proxy',
80778     alternateClassName: 'Ext.data.ClientProxy',
80779
80780     /**
80781      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
80782      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
80783      */
80784     clear: function() {
80785         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.");
80786     }
80787 });
80788 /**
80789  * @author Ed Spencer
80790  *
80791  * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
80792  * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
80793  * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
80794  *
80795  * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
80796  * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
80797  * injected might look like this:
80798  *
80799  *     <script src="http://domainB.com/users?callback=someCallback"></script>
80800  *
80801  * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
80802  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
80803  * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
80804  * long as the server formats the response to look like this, everything will work:
80805  *
80806  *     someCallback({
80807  *         users: [
80808  *             {
80809  *                 id: 1,
80810  *                 name: "Ed Spencer",
80811  *                 email: "ed@sencha.com"
80812  *             }
80813  *         ]
80814  *     });
80815  *
80816  * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
80817  * object that the server returned.
80818  *
80819  * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
80820  * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
80821  * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
80822  * we might set that up:
80823  *
80824  *     Ext.define('User', {
80825  *         extend: 'Ext.data.Model',
80826  *         fields: ['id', 'name', 'email']
80827  *     });
80828  *
80829  *     var store = Ext.create('Ext.data.Store', {
80830  *         model: 'User',
80831  *         proxy: {
80832  *             type: 'jsonp',
80833  *             url : 'http://domainB.com/users'
80834  *         }
80835  *     });
80836  *
80837  *     store.load();
80838  *
80839  * That's all we need to do - JsonP proxy takes care of the rest. In this case the Proxy will have injected a script tag
80840  * like this:
80841  *
80842  *     <script src="http://domainB.com/users?callback=callback1"></script>
80843  *
80844  * # Customization
80845  *
80846  * This script tag can be customized using the {@link #callbackKey} configuration. For example:
80847  *
80848  *     var store = Ext.create('Ext.data.Store', {
80849  *         model: 'User',
80850  *         proxy: {
80851  *             type: 'jsonp',
80852  *             url : 'http://domainB.com/users',
80853  *             callbackKey: 'theCallbackFunction'
80854  *         }
80855  *     });
80856  *
80857  *     store.load();
80858  *
80859  * Would inject a script tag like this:
80860  *
80861  *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
80862  *
80863  * # Implementing on the server side
80864  *
80865  * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
80866  * achieve this using Java, PHP and ASP.net:
80867  *
80868  * Java:
80869  *
80870  *     boolean jsonP = false;
80871  *     String cb = request.getParameter("callback");
80872  *     if (cb != null) {
80873  *         jsonP = true;
80874  *         response.setContentType("text/javascript");
80875  *     } else {
80876  *         response.setContentType("application/x-json");
80877  *     }
80878  *     Writer out = response.getWriter();
80879  *     if (jsonP) {
80880  *         out.write(cb + "(");
80881  *     }
80882  *     out.print(dataBlock.toJsonString());
80883  *     if (jsonP) {
80884  *         out.write(");");
80885  *     }
80886  *
80887  * PHP:
80888  *
80889  *     $callback = $_REQUEST['callback'];
80890  *
80891  *     // Create the output object.
80892  *     $output = array('a' => 'Apple', 'b' => 'Banana');
80893  *
80894  *     //start output
80895  *     if ($callback) {
80896  *         header('Content-Type: text/javascript');
80897  *         echo $callback . '(' . json_encode($output) . ');';
80898  *     } else {
80899  *         header('Content-Type: application/x-json');
80900  *         echo json_encode($output);
80901  *     }
80902  *
80903  * ASP.net:
80904  *
80905  *     String jsonString = "{success: true}";
80906  *     String cb = Request.Params.Get("callback");
80907  *     String responseString = "";
80908  *     if (!String.IsNullOrEmpty(cb)) {
80909  *         responseString = cb + "(" + jsonString + ")";
80910  *     } else {
80911  *         responseString = jsonString;
80912  *     }
80913  *     Response.Write(responseString);
80914  */
80915 Ext.define('Ext.data.proxy.JsonP', {
80916     extend: 'Ext.data.proxy.Server',
80917     alternateClassName: 'Ext.data.ScriptTagProxy',
80918     alias: ['proxy.jsonp', 'proxy.scripttag'],
80919     requires: ['Ext.data.JsonP'],
80920
80921     defaultWriterType: 'base',
80922
80923     /**
80924      * @cfg {String} callbackKey
80925      * See {@link Ext.data.JsonP#callbackKey}.
80926      */
80927     callbackKey : 'callback',
80928
80929     /**
80930      * @cfg {String} recordParam
80931      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
80932      * 'records'
80933      */
80934     recordParam: 'records',
80935
80936     /**
80937      * @cfg {Boolean} autoAppendParams
80938      * True to automatically append the request's params to the generated url. Defaults to true
80939      */
80940     autoAppendParams: true,
80941
80942     constructor: function(){
80943         this.addEvents(
80944             /**
80945              * @event
80946              * Fires when the server returns an exception
80947              * @param {Ext.data.proxy.Proxy} this
80948              * @param {Ext.data.Request} request The request that was sent
80949              * @param {Ext.data.Operation} operation The operation that triggered the request
80950              */
80951             'exception'
80952         );
80953         this.callParent(arguments);
80954     },
80955
80956     /**
80957      * @private
80958      * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
80959      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
80960      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
80961      * @param {Function} callback A callback function to execute when the Operation has been completed
80962      * @param {Object} scope The scope to execute the callback in
80963      */
80964     doRequest: function(operation, callback, scope) {
80965         //generate the unique IDs for this request
80966         var me      = this,
80967             writer  = me.getWriter(),
80968             request = me.buildRequest(operation),
80969             params = request.params;
80970
80971         if (operation.allowWrite()) {
80972             request = writer.write(request);
80973         }
80974
80975         // apply JsonP proxy-specific attributes to the Request
80976         Ext.apply(request, {
80977             callbackKey: me.callbackKey,
80978             timeout: me.timeout,
80979             scope: me,
80980             disableCaching: false, // handled by the proxy
80981             callback: me.createRequestCallback(request, operation, callback, scope)
80982         });
80983
80984         // prevent doubling up
80985         if (me.autoAppendParams) {
80986             request.params = {};
80987         }
80988
80989         request.jsonp = Ext.data.JsonP.request(request);
80990         // restore on the request
80991         request.params = params;
80992         operation.setStarted();
80993         me.lastRequest = request;
80994
80995         return request;
80996     },
80997
80998     /**
80999      * @private
81000      * Creates and returns the function that is called when the request has completed. The returned function
81001      * should accept a Response object, which contains the response to be read by the configured Reader.
81002      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
81003      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
81004      * theCallback refers to the callback argument received by this function.
81005      * See {@link #doRequest} for details.
81006      * @param {Ext.data.Request} request The Request object
81007      * @param {Ext.data.Operation} operation The Operation being executed
81008      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
81009      * passed to doRequest
81010      * @param {Object} scope The scope in which to execute the callback function
81011      * @return {Function} The callback function
81012      */
81013     createRequestCallback: function(request, operation, callback, scope) {
81014         var me = this;
81015
81016         return function(success, response, errorType) {
81017             delete me.lastRequest;
81018             me.processResponse(success, operation, request, response, callback, scope);
81019         };
81020     },
81021
81022     // inherit docs
81023     setException: function(operation, response) {
81024         operation.setException(operation.request.jsonp.errorType);
81025     },
81026
81027
81028     /**
81029      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
81030      * @param {Ext.data.Request} request The request object
81031      * @return {String} The url
81032      */
81033     buildUrl: function(request) {
81034         var me      = this,
81035             url     = me.callParent(arguments),
81036             params  = Ext.apply({}, request.params),
81037             filters = params.filters,
81038             records,
81039             filter, i;
81040
81041         delete params.filters;
81042
81043         if (me.autoAppendParams) {
81044             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
81045         }
81046
81047         if (filters && filters.length) {
81048             for (i = 0; i < filters.length; i++) {
81049                 filter = filters[i];
81050
81051                 if (filter.value) {
81052                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
81053                 }
81054             }
81055         }
81056
81057         //if there are any records present, append them to the url also
81058         records = request.records;
81059
81060         if (Ext.isArray(records) && records.length > 0) {
81061             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
81062         }
81063
81064         return url;
81065     },
81066
81067     //inherit docs
81068     destroy: function() {
81069         this.abort();
81070         this.callParent();
81071     },
81072
81073     /**
81074      * Aborts the current server request if one is currently running
81075      */
81076     abort: function() {
81077         var lastRequest = this.lastRequest;
81078         if (lastRequest) {
81079             Ext.data.JsonP.abort(lastRequest.jsonp);
81080         }
81081     },
81082
81083     /**
81084      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
81085      * its own function so that it can be easily overridden.
81086      * @param {Ext.data.Model[]} records The records array
81087      * @return {String} The encoded records string
81088      */
81089     encodeRecords: function(records) {
81090         var encoded = "",
81091             i = 0,
81092             len = records.length;
81093
81094         for (; i < len; i++) {
81095             encoded += Ext.Object.toQueryString(records[i].data);
81096         }
81097
81098         return encoded;
81099     }
81100 });
81101
81102 /**
81103  * @author Ed Spencer
81104  *
81105  * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
81106  * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
81107  * save {@link Ext.data.Model model instances} for offline use.
81108  * @private
81109  */
81110 Ext.define('Ext.data.proxy.WebStorage', {
81111     extend: 'Ext.data.proxy.Client',
81112     alternateClassName: 'Ext.data.WebStorageProxy',
81113
81114     /**
81115      * @cfg {String} id
81116      * The unique ID used as the key in which all record data are stored in the local storage object.
81117      */
81118     id: undefined,
81119
81120     /**
81121      * Creates the proxy, throws an error if local storage is not supported in the current browser.
81122      * @param {Object} config (optional) Config object.
81123      */
81124     constructor: function(config) {
81125         this.callParent(arguments);
81126
81127         /**
81128          * @property {Object} cache
81129          * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
81130          */
81131         this.cache = {};
81132
81133         if (this.getStorageObject() === undefined) {
81134             Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
81135         }
81136
81137         //if an id is not given, try to use the store's id instead
81138         this.id = this.id || (this.store ? this.store.storeId : undefined);
81139
81140         if (this.id === undefined) {
81141             Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
81142         }
81143
81144         this.initialize();
81145     },
81146
81147     //inherit docs
81148     create: function(operation, callback, scope) {
81149         var records = operation.records,
81150             length  = records.length,
81151             ids     = this.getIds(),
81152             id, record, i;
81153
81154         operation.setStarted();
81155
81156         for (i = 0; i < length; i++) {
81157             record = records[i];
81158
81159             if (record.phantom) {
81160                 record.phantom = false;
81161                 id = this.getNextId();
81162             } else {
81163                 id = record.getId();
81164             }
81165
81166             this.setRecord(record, id);
81167             ids.push(id);
81168         }
81169
81170         this.setIds(ids);
81171
81172         operation.setCompleted();
81173         operation.setSuccessful();
81174
81175         if (typeof callback == 'function') {
81176             callback.call(scope || this, operation);
81177         }
81178     },
81179
81180     //inherit docs
81181     read: function(operation, callback, scope) {
81182         //TODO: respect sorters, filters, start and limit options on the Operation
81183
81184         var records = [],
81185             ids     = this.getIds(),
81186             length  = ids.length,
81187             i, recordData, record;
81188
81189         //read a single record
81190         if (operation.id) {
81191             record = this.getRecord(operation.id);
81192
81193             if (record) {
81194                 records.push(record);
81195                 operation.setSuccessful();
81196             }
81197         } else {
81198             for (i = 0; i < length; i++) {
81199                 records.push(this.getRecord(ids[i]));
81200             }
81201             operation.setSuccessful();
81202         }
81203
81204         operation.setCompleted();
81205
81206         operation.resultSet = Ext.create('Ext.data.ResultSet', {
81207             records: records,
81208             total  : records.length,
81209             loaded : true
81210         });
81211
81212         if (typeof callback == 'function') {
81213             callback.call(scope || this, operation);
81214         }
81215     },
81216
81217     //inherit docs
81218     update: function(operation, callback, scope) {
81219         var records = operation.records,
81220             length  = records.length,
81221             ids     = this.getIds(),
81222             record, id, i;
81223
81224         operation.setStarted();
81225
81226         for (i = 0; i < length; i++) {
81227             record = records[i];
81228             this.setRecord(record);
81229
81230             //we need to update the set of ids here because it's possible that a non-phantom record was added
81231             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
81232             id = record.getId();
81233             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
81234                 ids.push(id);
81235             }
81236         }
81237         this.setIds(ids);
81238
81239         operation.setCompleted();
81240         operation.setSuccessful();
81241
81242         if (typeof callback == 'function') {
81243             callback.call(scope || this, operation);
81244         }
81245     },
81246
81247     //inherit
81248     destroy: function(operation, callback, scope) {
81249         var records = operation.records,
81250             length  = records.length,
81251             ids     = this.getIds(),
81252
81253             //newIds is a copy of ids, from which we remove the destroyed records
81254             newIds  = [].concat(ids),
81255             i;
81256
81257         for (i = 0; i < length; i++) {
81258             Ext.Array.remove(newIds, records[i].getId());
81259             this.removeRecord(records[i], false);
81260         }
81261
81262         this.setIds(newIds);
81263
81264         operation.setCompleted();
81265         operation.setSuccessful();
81266
81267         if (typeof callback == 'function') {
81268             callback.call(scope || this, operation);
81269         }
81270     },
81271
81272     /**
81273      * @private
81274      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
81275      * @param {String} id The record's unique ID
81276      * @return {Ext.data.Model} The model instance
81277      */
81278     getRecord: function(id) {
81279         if (this.cache[id] === undefined) {
81280             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
81281                 data    = {},
81282                 Model   = this.model,
81283                 fields  = Model.prototype.fields.items,
81284                 length  = fields.length,
81285                 i, field, name, record;
81286
81287             for (i = 0; i < length; i++) {
81288                 field = fields[i];
81289                 name  = field.name;
81290
81291                 if (typeof field.decode == 'function') {
81292                     data[name] = field.decode(rawData[name]);
81293                 } else {
81294                     data[name] = rawData[name];
81295                 }
81296             }
81297
81298             record = new Model(data, id);
81299             record.phantom = false;
81300
81301             this.cache[id] = record;
81302         }
81303
81304         return this.cache[id];
81305     },
81306
81307     /**
81308      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
81309      * @param {Ext.data.Model} record The model instance
81310      * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
81311      */
81312     setRecord: function(record, id) {
81313         if (id) {
81314             record.setId(id);
81315         } else {
81316             id = record.getId();
81317         }
81318
81319         var me = this,
81320             rawData = record.data,
81321             data    = {},
81322             model   = me.model,
81323             fields  = model.prototype.fields.items,
81324             length  = fields.length,
81325             i = 0,
81326             field, name, obj, key;
81327
81328         for (; i < length; i++) {
81329             field = fields[i];
81330             name  = field.name;
81331
81332             if (typeof field.encode == 'function') {
81333                 data[name] = field.encode(rawData[name], record);
81334             } else {
81335                 data[name] = rawData[name];
81336             }
81337         }
81338
81339         obj = me.getStorageObject();
81340         key = me.getRecordKey(id);
81341
81342         //keep the cache up to date
81343         me.cache[id] = record;
81344
81345         //iPad bug requires that we remove the item before setting it
81346         obj.removeItem(key);
81347         obj.setItem(key, Ext.encode(data));
81348     },
81349
81350     /**
81351      * @private
81352      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
81353      * use instead because it updates the list of currently-stored record ids
81354      * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
81355      */
81356     removeRecord: function(id, updateIds) {
81357         var me = this,
81358             ids;
81359
81360         if (id.isModel) {
81361             id = id.getId();
81362         }
81363
81364         if (updateIds !== false) {
81365             ids = me.getIds();
81366             Ext.Array.remove(ids, id);
81367             me.setIds(ids);
81368         }
81369
81370         me.getStorageObject().removeItem(me.getRecordKey(id));
81371     },
81372
81373     /**
81374      * @private
81375      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
81376      * storing data in the local storage object and should prevent naming collisions.
81377      * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
81378      * @return {String} The unique key for this record
81379      */
81380     getRecordKey: function(id) {
81381         if (id.isModel) {
81382             id = id.getId();
81383         }
81384
81385         return Ext.String.format("{0}-{1}", this.id, id);
81386     },
81387
81388     /**
81389      * @private
81390      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
81391      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
81392      * @return {String} The counter key
81393      */
81394     getRecordCounterKey: function() {
81395         return Ext.String.format("{0}-counter", this.id);
81396     },
81397
81398     /**
81399      * @private
81400      * Returns the array of record IDs stored in this Proxy
81401      * @return {Number[]} The record IDs. Each is cast as a Number
81402      */
81403     getIds: function() {
81404         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
81405             length = ids.length,
81406             i;
81407
81408         if (length == 1 && ids[0] === "") {
81409             ids = [];
81410         } else {
81411             for (i = 0; i < length; i++) {
81412                 ids[i] = parseInt(ids[i], 10);
81413             }
81414         }
81415
81416         return ids;
81417     },
81418
81419     /**
81420      * @private
81421      * Saves the array of ids representing the set of all records in the Proxy
81422      * @param {Number[]} ids The ids to set
81423      */
81424     setIds: function(ids) {
81425         var obj = this.getStorageObject(),
81426             str = ids.join(",");
81427
81428         obj.removeItem(this.id);
81429
81430         if (!Ext.isEmpty(str)) {
81431             obj.setItem(this.id, str);
81432         }
81433     },
81434
81435     /**
81436      * @private
81437      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
81438      * Increments the counter.
81439      * @return {Number} The id
81440      */
81441     getNextId: function() {
81442         var obj  = this.getStorageObject(),
81443             key  = this.getRecordCounterKey(),
81444             last = obj.getItem(key),
81445             ids, id;
81446
81447         if (last === null) {
81448             ids = this.getIds();
81449             last = ids[ids.length - 1] || 0;
81450         }
81451
81452         id = parseInt(last, 10) + 1;
81453         obj.setItem(key, id);
81454
81455         return id;
81456     },
81457
81458     /**
81459      * @private
81460      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
81461      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
81462      */
81463     initialize: function() {
81464         var storageObject = this.getStorageObject();
81465         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
81466     },
81467
81468     /**
81469      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
81470      * storage object.
81471      */
81472     clear: function() {
81473         var obj = this.getStorageObject(),
81474             ids = this.getIds(),
81475             len = ids.length,
81476             i;
81477
81478         //remove all the records
81479         for (i = 0; i < len; i++) {
81480             this.removeRecord(ids[i]);
81481         }
81482
81483         //remove the supporting objects
81484         obj.removeItem(this.getRecordCounterKey());
81485         obj.removeItem(this.id);
81486     },
81487
81488     /**
81489      * @private
81490      * Abstract function which should return the storage object that data will be saved to. This must be implemented
81491      * in each subclass.
81492      * @return {Object} The storage object
81493      */
81494     getStorageObject: function() {
81495         Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
81496     }
81497 });
81498 /**
81499  * @author Ed Spencer
81500  *
81501  * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
81502  * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
81503  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
81504  *
81505  * localStorage is extremely useful for saving user-specific information without needing to build server-side
81506  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
81507  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
81508  *
81509  *     Ext.define('Search', {
81510  *         fields: ['id', 'query'],
81511  *         extend: 'Ext.data.Model',
81512  *         proxy: {
81513  *             type: 'localstorage',
81514  *             id  : 'twitter-Searches'
81515  *         }
81516  *     });
81517  *
81518  * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
81519  * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
81520  * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
81521  * LocalStorageProxy to manage the saved Search data.
81522  *
81523  * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
81524  *
81525  *     //our Store automatically picks up the LocalStorageProxy defined on the Search model
81526  *     var store = Ext.create('Ext.data.Store', {
81527  *         model: "Search"
81528  *     });
81529  *
81530  *     //loads any existing Search data from localStorage
81531  *     store.load();
81532  *
81533  *     //now add some Searches
81534  *     store.add({query: 'Sencha Touch'});
81535  *     store.add({query: 'Ext JS'});
81536  *
81537  *     //finally, save our Search data to localStorage
81538  *     store.sync();
81539  *
81540  * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
81541  * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
81542  *
81543  *     var search = Ext.create('Search', {query: 'Sencha Animator'});
81544  *
81545  *     //uses the configured LocalStorageProxy to save the new Search to localStorage
81546  *     search.save();
81547  *
81548  * # Limitations
81549  *
81550  * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
81551  * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
81552  * object.
81553  *
81554  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81555  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81556  */
81557 Ext.define('Ext.data.proxy.LocalStorage', {
81558     extend: 'Ext.data.proxy.WebStorage',
81559     alias: 'proxy.localstorage',
81560     alternateClassName: 'Ext.data.LocalStorageProxy',
81561     
81562     //inherit docs
81563     getStorageObject: function() {
81564         return window.localStorage;
81565     }
81566 });
81567 /**
81568  * @author Ed Spencer
81569  *
81570  * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
81571  * every page refresh.
81572  *
81573  * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
81574  * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
81575  * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
81576  * Store:
81577  *
81578  *     //this is the model we will be using in the store
81579  *     Ext.define('User', {
81580  *         extend: 'Ext.data.Model',
81581  *         fields: [
81582  *             {name: 'id',    type: 'int'},
81583  *             {name: 'name',  type: 'string'},
81584  *             {name: 'phone', type: 'string', mapping: 'phoneNumber'}
81585  *         ]
81586  *     });
81587  *
81588  *     //this data does not line up to our model fields - the phone field is called phoneNumber
81589  *     var data = {
81590  *         users: [
81591  *             {
81592  *                 id: 1,
81593  *                 name: 'Ed Spencer',
81594  *                 phoneNumber: '555 1234'
81595  *             },
81596  *             {
81597  *                 id: 2,
81598  *                 name: 'Abe Elias',
81599  *                 phoneNumber: '666 1234'
81600  *             }
81601  *         ]
81602  *     };
81603  *
81604  *     //note how we set the 'root' in the reader to match the data structure above
81605  *     var store = Ext.create('Ext.data.Store', {
81606  *         autoLoad: true,
81607  *         model: 'User',
81608  *         data : data,
81609  *         proxy: {
81610  *             type: 'memory',
81611  *             reader: {
81612  *                 type: 'json',
81613  *                 root: 'users'
81614  *             }
81615  *         }
81616  *     });
81617  */
81618 Ext.define('Ext.data.proxy.Memory', {
81619     extend: 'Ext.data.proxy.Client',
81620     alias: 'proxy.memory',
81621     alternateClassName: 'Ext.data.MemoryProxy',
81622
81623     /**
81624      * @cfg {Ext.data.Model[]} data
81625      * Optional array of Records to load into the Proxy
81626      */
81627
81628     constructor: function(config) {
81629         this.callParent([config]);
81630
81631         //ensures that the reader has been instantiated properly
81632         this.setReader(this.reader);
81633     },
81634
81635     /**
81636      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
81637      * @param {Ext.data.Operation} operation The read Operation
81638      * @param {Function} callback The callback to call when reading has completed
81639      * @param {Object} scope The scope to call the callback function in
81640      */
81641     read: function(operation, callback, scope) {
81642         var me     = this,
81643             reader = me.getReader(),
81644             result = reader.read(me.data);
81645
81646         Ext.apply(operation, {
81647             resultSet: result
81648         });
81649
81650         operation.setCompleted();
81651         operation.setSuccessful();
81652         Ext.callback(callback, scope || me, [operation]);
81653     },
81654
81655     clear: Ext.emptyFn
81656 });
81657
81658 /**
81659  * @author Ed Spencer
81660  *
81661  * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
81662  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
81663  * with an inline Rest proxy
81664  *
81665  *     Ext.define('User', {
81666  *         extend: 'Ext.data.Model',
81667  *         fields: ['id', 'name', 'email'],
81668  *
81669  *         proxy: {
81670  *             type: 'rest',
81671  *             url : '/users'
81672  *         }
81673  *     });
81674  *
81675  * Now we can create a new User instance and save it via the Rest proxy. Doing this will cause the Proxy to send a POST
81676  * request to '/users':
81677  *
81678  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
81679  *
81680  *     user.save(); //POST /users
81681  *
81682  * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
81683  * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
81684  *
81685  *     user.save({
81686  *         success: function(user) {
81687  *             user.set('name', 'Khan Noonien Singh');
81688  *
81689  *             user.save(); //PUT /users/123
81690  *         }
81691  *     });
81692  *
81693  * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
81694  * relevant url for that user. Now let's delete this user, which will use the DELETE method:
81695  *
81696  *         user.destroy(); //DELETE /users/123
81697  *
81698  * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
81699  *
81700  *     //1. Load via Store
81701  *
81702  *     //the Store automatically picks up the Proxy from the User model
81703  *     var store = Ext.create('Ext.data.Store', {
81704  *         model: 'User'
81705  *     });
81706  *
81707  *     store.load(); //GET /users
81708  *
81709  *     //2. Load directly from the Model
81710  *
81711  *     //GET /users/123
81712  *     Ext.ModelManager.getModel('User').load(123, {
81713  *         success: function(user) {
81714  *             console.log(user.getId()); //outputs 123
81715  *         }
81716  *     });
81717  *
81718  * # Url generation
81719  *
81720  * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
81721  * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
81722  * instance in question to the configured url, resulting in the '/users/123' that we saw above.
81723  *
81724  * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
81725  * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
81726  *
81727  *     new Ext.data.proxy.Rest({
81728  *         url: '/users',
81729  *         appendId: true //default
81730  *     });
81731  *
81732  *     // Collection url: /users
81733  *     // Instance url  : /users/123
81734  *
81735  * The Rest proxy can also optionally append a format string to the end of any generated url:
81736  *
81737  *     new Ext.data.proxy.Rest({
81738  *         url: '/users',
81739  *         format: 'json'
81740  *     });
81741  *
81742  *     // Collection url: /users.json
81743  *     // Instance url  : /users/123.json
81744  *
81745  * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
81746  * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
81747  * an example of how to achieve this.
81748  *
81749  * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
81750  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
81751  * details.
81752  *
81753  * [1]: source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl
81754  */
81755 Ext.define('Ext.data.proxy.Rest', {
81756     extend: 'Ext.data.proxy.Ajax',
81757     alternateClassName: 'Ext.data.RestProxy',
81758     alias : 'proxy.rest',
81759     
81760     /**
81761      * @cfg {Boolean} appendId
81762      * True to automatically append the ID of a Model instance when performing a request based on that single instance.
81763      * See Rest proxy intro docs for more details. Defaults to true.
81764      */
81765     appendId: true,
81766     
81767     /**
81768      * @cfg {String} format
81769      * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
81770      * for full details. Defaults to undefined.
81771      */
81772     
81773     /**
81774      * @cfg {Boolean} batchActions
81775      * True to batch actions of a particular type when synchronizing the store. Defaults to false.
81776      */
81777     batchActions: false,
81778     
81779     /**
81780      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
81781      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
81782      * that additional parameters like the cache buster string are appended.
81783      * @param {Object} request
81784      */
81785     buildUrl: function(request) {
81786         var me        = this,
81787             operation = request.operation,
81788             records   = operation.records || [],
81789             record    = records[0],
81790             format    = me.format,
81791             url       = me.getUrl(request),
81792             id        = record ? record.getId() : operation.id;
81793         
81794         if (me.appendId && id) {
81795             if (!url.match(/\/$/)) {
81796                 url += '/';
81797             }
81798             
81799             url += id;
81800         }
81801         
81802         if (format) {
81803             if (!url.match(/\.$/)) {
81804                 url += '.';
81805             }
81806             
81807             url += format;
81808         }
81809         
81810         request.url = url;
81811         
81812         return me.callParent(arguments);
81813     }
81814 }, function() {
81815     Ext.apply(this.prototype, {
81816         /**
81817          * @property {Object} actionMethods
81818          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
81819          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
81820          * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
81821          * can be overridden instead.
81822          */
81823         actionMethods: {
81824             create : 'POST',
81825             read   : 'GET',
81826             update : 'PUT',
81827             destroy: 'DELETE'
81828         }
81829     });
81830 });
81831
81832 /**
81833  * @author Ed Spencer
81834  *
81835  * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
81836  * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
81837  * unique ID which is used as a key in which all record data are stored in the session storage object.
81838  *
81839  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81840  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81841  *
81842  * Proxies are almost always used with a {@link Ext.data.Store store}:
81843  *
81844  *     new Ext.data.Store({
81845  *         proxy: {
81846  *             type: 'sessionstorage',
81847  *             id  : 'myProxyKey'
81848  *         }
81849  *     });
81850  *
81851  * Alternatively you can instantiate the Proxy directly:
81852  *
81853  *     new Ext.data.proxy.SessionStorage({
81854  *         id  : 'myOtherProxyKey'
81855  *     });
81856  *
81857  * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
81858  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
81859  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
81860  */
81861 Ext.define('Ext.data.proxy.SessionStorage', {
81862     extend: 'Ext.data.proxy.WebStorage',
81863     alias: 'proxy.sessionstorage',
81864     alternateClassName: 'Ext.data.SessionStorageProxy',
81865     
81866     //inherit docs
81867     getStorageObject: function() {
81868         return window.sessionStorage;
81869     }
81870 });
81871
81872 /**
81873  * @author Ed Spencer
81874  * @class Ext.data.reader.Array
81875  * @extends Ext.data.reader.Json
81876  * 
81877  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
81878  * Each element of that Array represents a row of data fields. The
81879  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
81880  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
81881  * 
81882  * <p><u>Example code:</u></p>
81883  * 
81884 <pre><code>
81885 Employee = Ext.define('Employee', {
81886     extend: 'Ext.data.Model',
81887     fields: [
81888         'id',
81889         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
81890         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
81891     ]
81892 });
81893
81894 var myReader = new Ext.data.reader.Array({
81895     model: 'Employee'
81896 }, Employee);
81897 </code></pre>
81898  * 
81899  * <p>This would consume an Array like this:</p>
81900  * 
81901 <pre><code>
81902 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
81903 </code></pre>
81904  * 
81905  * @constructor
81906  * Create a new ArrayReader
81907  * @param {Object} meta Metadata configuration options.
81908  */
81909 Ext.define('Ext.data.reader.Array', {
81910     extend: 'Ext.data.reader.Json',
81911     alternateClassName: 'Ext.data.ArrayReader',
81912     alias : 'reader.array',
81913
81914     /**
81915      * @private
81916      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
81917      * reference the correct position in the array.
81918      */
81919     buildExtractors: function() {
81920         this.callParent(arguments);
81921         
81922         var fields = this.model.prototype.fields.items,
81923             i = 0,
81924             length = fields.length,
81925             extractorFunctions = [],
81926             map;
81927         
81928         for (; i < length; i++) {
81929             map = fields[i].mapping;
81930             extractorFunctions.push(function(index) {
81931                 return function(data) {
81932                     return data[index];
81933                 };
81934             }(map !== null ? map : i));
81935         }
81936         
81937         this.extractorFunctions = extractorFunctions;
81938     }
81939 });
81940
81941 /**
81942  * @author Ed Spencer
81943  * @class Ext.data.reader.Xml
81944  * @extends Ext.data.reader.Reader
81945  *
81946  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
81947  * happens as a result of loading a Store - for example we might create something like this:</p>
81948  *
81949 <pre><code>
81950 Ext.define('User', {
81951     extend: 'Ext.data.Model',
81952     fields: ['id', 'name', 'email']
81953 });
81954
81955 var store = Ext.create('Ext.data.Store', {
81956     model: 'User',
81957     proxy: {
81958         type: 'ajax',
81959         url : 'users.xml',
81960         reader: {
81961             type: 'xml',
81962             record: 'user'
81963         }
81964     }
81965 });
81966 </code></pre>
81967  *
81968  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
81969  * not already familiar with them.</p>
81970  *
81971  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
81972  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
81973  * Store, so it is as if we passed this instead:
81974  *
81975 <pre><code>
81976 reader: {
81977     type : 'xml',
81978     model: 'User',
81979     record: 'user'
81980 }
81981 </code></pre>
81982  *
81983  * <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>
81984  *
81985 <pre><code>
81986 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81987 &lt;user&gt;
81988     &lt;id&gt;1&lt;/id&gt;
81989     &lt;name&gt;Ed Spencer&lt;/name&gt;
81990     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81991 &lt;/user&gt;
81992 &lt;user&gt;
81993     &lt;id&gt;2&lt;/id&gt;
81994     &lt;name&gt;Abe Elias&lt;/name&gt;
81995     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81996 &lt;/user&gt;
81997 </code></pre>
81998  *
81999  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
82000  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
82001  *
82002  * <p><u>Reading other XML formats</u></p>
82003  *
82004  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
82005  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
82006  * {@link #root} configuration to parse data that comes back like this:</p>
82007  *
82008 <pre><code>
82009 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82010 &lt;users&gt;
82011     &lt;user&gt;
82012         &lt;id&gt;1&lt;/id&gt;
82013         &lt;name&gt;Ed Spencer&lt;/name&gt;
82014         &lt;email&gt;ed@sencha.com&lt;/email&gt;
82015     &lt;/user&gt;
82016     &lt;user&gt;
82017         &lt;id&gt;2&lt;/id&gt;
82018         &lt;name&gt;Abe Elias&lt;/name&gt;
82019         &lt;email&gt;abe@sencha.com&lt;/email&gt;
82020     &lt;/user&gt;
82021 &lt;/users&gt;
82022 </code></pre>
82023  *
82024  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
82025  *
82026 <pre><code>
82027 reader: {
82028     type  : 'xml',
82029     root  : 'users',
82030     record: 'user'
82031 }
82032 </code></pre>
82033  *
82034  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
82035  * a larger structure, so a response like this will still work:
82036  *
82037 <pre><code>
82038 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82039 &lt;deeply&gt;
82040     &lt;nested&gt;
82041         &lt;xml&gt;
82042             &lt;users&gt;
82043                 &lt;user&gt;
82044                     &lt;id&gt;1&lt;/id&gt;
82045                     &lt;name&gt;Ed Spencer&lt;/name&gt;
82046                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
82047                 &lt;/user&gt;
82048                 &lt;user&gt;
82049                     &lt;id&gt;2&lt;/id&gt;
82050                     &lt;name&gt;Abe Elias&lt;/name&gt;
82051                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
82052                 &lt;/user&gt;
82053             &lt;/users&gt;
82054         &lt;/xml&gt;
82055     &lt;/nested&gt;
82056 &lt;/deeply&gt;
82057 </code></pre>
82058  *
82059  * <p><u>Response metadata</u></p>
82060  *
82061  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
82062  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
82063  * like this:</p>
82064  *
82065 <pre><code>
82066 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
82067 &lt;total&gt;100&lt;/total&gt;
82068 &lt;success&gt;true&lt;/success&gt;
82069 &lt;users&gt;
82070     &lt;user&gt;
82071         &lt;id&gt;1&lt;/id&gt;
82072         &lt;name&gt;Ed Spencer&lt;/name&gt;
82073         &lt;email&gt;ed@sencha.com&lt;/email&gt;
82074     &lt;/user&gt;
82075     &lt;user&gt;
82076         &lt;id&gt;2&lt;/id&gt;
82077         &lt;name&gt;Abe Elias&lt;/name&gt;
82078         &lt;email&gt;abe@sencha.com&lt;/email&gt;
82079     &lt;/user&gt;
82080 &lt;/users&gt;
82081 </code></pre>
82082  *
82083  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
82084  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
82085  * options:</p>
82086  *
82087 <pre><code>
82088 reader: {
82089     type: 'xml',
82090     root: 'users',
82091     totalProperty  : 'total',
82092     successProperty: 'success'
82093 }
82094 </code></pre>
82095  *
82096  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
82097  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
82098  * returned.</p>
82099  *
82100  * <p><u>Response format</u></p>
82101  *
82102  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP
82103  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
82104  * work correctly otherwise.</p>
82105  */
82106 Ext.define('Ext.data.reader.Xml', {
82107     extend: 'Ext.data.reader.Reader',
82108     alternateClassName: 'Ext.data.XmlReader',
82109     alias : 'reader.xml',
82110
82111     /**
82112      * @cfg {String} record (required)
82113      * The DomQuery path to the repeated element which contains record information.
82114      */
82115
82116     /**
82117      * @private
82118      * Creates a function to return some particular key of data from a response. The totalProperty and
82119      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
82120      * @param {String} key
82121      * @return {Function}
82122      */
82123     createAccessor: function(expr) {
82124         var me = this;
82125
82126         if (Ext.isEmpty(expr)) {
82127             return Ext.emptyFn;
82128         }
82129
82130         if (Ext.isFunction(expr)) {
82131             return expr;
82132         }
82133
82134         return function(root) {
82135             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
82136         };
82137     },
82138
82139     getNodeValue: function(node) {
82140         if (node && node.firstChild) {
82141             return node.firstChild.nodeValue;
82142         }
82143         return undefined;
82144     },
82145
82146     //inherit docs
82147     getResponseData: function(response) {
82148         var xml = response.responseXML;
82149
82150         if (!xml) {
82151             Ext.Error.raise({
82152                 response: response,
82153                 msg: 'XML data not found in the response'
82154             });
82155         }
82156
82157         return xml;
82158     },
82159
82160     /**
82161      * Normalizes the data object
82162      * @param {Object} data The raw data object
82163      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
82164      */
82165     getData: function(data) {
82166         return data.documentElement || data;
82167     },
82168
82169     /**
82170      * @private
82171      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
82172      * @param {Object} data The XML data object
82173      * @return {XMLElement} The root node element
82174      */
82175     getRoot: function(data) {
82176         var nodeName = data.nodeName,
82177             root     = this.root;
82178
82179         if (!root || (nodeName && nodeName == root)) {
82180             return data;
82181         } else if (Ext.DomQuery.isXml(data)) {
82182             // This fix ensures we have XML data
82183             // Related to TreeStore calling getRoot with the root node, which isn't XML
82184             // Probably should be resolved in TreeStore at some point
82185             return Ext.DomQuery.selectNode(root, data);
82186         }
82187     },
82188
82189     /**
82190      * @private
82191      * We're just preparing the data for the superclass by pulling out the record nodes we want
82192      * @param {XMLElement} root The XML root node
82193      * @return {Ext.data.Model[]} The records
82194      */
82195     extractData: function(root) {
82196         var recordName = this.record;
82197
82198         if (!recordName) {
82199             Ext.Error.raise('Record is a required parameter');
82200         }
82201
82202         if (recordName != root.nodeName) {
82203             root = Ext.DomQuery.select(recordName, root);
82204         } else {
82205             root = [root];
82206         }
82207         return this.callParent([root]);
82208     },
82209
82210     /**
82211      * @private
82212      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
82213      * @param {Object} data The raw data object
82214      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
82215      * @return {XMLElement} The root
82216      */
82217     getAssociatedDataRoot: function(data, associationName) {
82218         return Ext.DomQuery.select(associationName, data)[0];
82219     },
82220
82221     /**
82222      * Parses an XML document and returns a ResultSet containing the model instances
82223      * @param {Object} doc Parsed XML document
82224      * @return {Ext.data.ResultSet} The parsed result set
82225      */
82226     readRecords: function(doc) {
82227         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
82228         if (Ext.isArray(doc)) {
82229             doc = doc[0];
82230         }
82231
82232         /**
82233          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
82234          * @property xmlData
82235          * @type Object
82236          */
82237         this.xmlData = doc;
82238         return this.callParent([doc]);
82239     }
82240 });
82241 /**
82242  * @author Ed Spencer
82243  * @class Ext.data.writer.Xml
82244  * @extends Ext.data.writer.Writer
82245
82246 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
82247 The {@link #documentRoot} property is used to specify the root element in the XML document.
82248 The {@link #record} option is used to specify the element name for each record that will make
82249 up the XML document.
82250
82251  * @markdown
82252  */
82253 Ext.define('Ext.data.writer.Xml', {
82254     
82255     /* Begin Definitions */
82256     
82257     extend: 'Ext.data.writer.Writer',
82258     alternateClassName: 'Ext.data.XmlWriter',
82259     
82260     alias: 'writer.xml',
82261     
82262     /* End Definitions */
82263     
82264     /**
82265      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
82266      * If there is more than 1 record and the root is not specified, the default document root will still be used
82267      * to ensure a valid XML document is created.
82268      */
82269     documentRoot: 'xmlData',
82270     
82271     /**
82272      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
82273      * to form a valid XML document.
82274      */
82275     defaultDocumentRoot: 'xmlData',
82276
82277     /**
82278      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
82279      * Defaults to <tt>''</tt>.
82280      */
82281     header: '',
82282
82283     /**
82284      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
82285      */
82286     record: 'record',
82287
82288     //inherit docs
82289     writeRecords: function(request, data) {
82290         var me = this,
82291             xml = [],
82292             i = 0,
82293             len = data.length,
82294             root = me.documentRoot,
82295             record = me.record,
82296             needsRoot = data.length !== 1,
82297             item,
82298             key;
82299             
82300         // may not exist
82301         xml.push(me.header || '');
82302         
82303         if (!root && needsRoot) {
82304             root = me.defaultDocumentRoot;
82305         }
82306         
82307         if (root) {
82308             xml.push('<', root, '>');
82309         }
82310             
82311         for (; i < len; ++i) {
82312             item = data[i];
82313             xml.push('<', record, '>');
82314             for (key in item) {
82315                 if (item.hasOwnProperty(key)) {
82316                     xml.push('<', key, '>', item[key], '</', key, '>');
82317                 }
82318             }
82319             xml.push('</', record, '>');
82320         }
82321         
82322         if (root) {
82323             xml.push('</', root, '>');
82324         }
82325             
82326         request.xmlData = xml.join('');
82327         return request;
82328     }
82329 });
82330
82331 /**
82332  * @class Ext.direct.Event
82333  * A base class for all Ext.direct events. An event is
82334  * created after some kind of interaction with the server.
82335  * The event class is essentially just a data structure
82336  * to hold a Direct response.
82337  */
82338 Ext.define('Ext.direct.Event', {
82339
82340     /* Begin Definitions */
82341
82342     alias: 'direct.event',
82343
82344     requires: ['Ext.direct.Manager'],
82345
82346     /* End Definitions */
82347
82348     status: true,
82349
82350     /**
82351      * Creates new Event.
82352      * @param {Object} config (optional) Config object.
82353      */
82354     constructor: function(config) {
82355         Ext.apply(this, config);
82356     },
82357
82358     /**
82359      * Return the raw data for this event.
82360      * @return {Object} The data from the event
82361      */
82362     getData: function(){
82363         return this.data;
82364     }
82365 });
82366
82367 /**
82368  * @class Ext.direct.RemotingEvent
82369  * @extends Ext.direct.Event
82370  * An event that is fired when data is received from a 
82371  * {@link Ext.direct.RemotingProvider}. Contains a method to the
82372  * related transaction for the direct request, see {@link #getTransaction}
82373  */
82374 Ext.define('Ext.direct.RemotingEvent', {
82375     
82376     /* Begin Definitions */
82377    
82378     extend: 'Ext.direct.Event',
82379     
82380     alias: 'direct.rpc',
82381     
82382     /* End Definitions */
82383     
82384     /**
82385      * Get the transaction associated with this event.
82386      * @return {Ext.direct.Transaction} The transaction
82387      */
82388     getTransaction: function(){
82389         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
82390     }
82391 });
82392
82393 /**
82394  * @class Ext.direct.ExceptionEvent
82395  * @extends Ext.direct.RemotingEvent
82396  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
82397  */
82398 Ext.define('Ext.direct.ExceptionEvent', {
82399     
82400     /* Begin Definitions */
82401    
82402     extend: 'Ext.direct.RemotingEvent',
82403     
82404     alias: 'direct.exception',
82405     
82406     /* End Definitions */
82407    
82408    status: false
82409 });
82410
82411 /**
82412  * @class Ext.direct.Provider
82413  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
82414  *
82415  * <p>For example Ext JS implements the following subclasses:</p>
82416  * <pre><code>
82417 Provider
82418 |
82419 +---{@link Ext.direct.JsonProvider JsonProvider}
82420     |
82421     +---{@link Ext.direct.PollingProvider PollingProvider}
82422     |
82423     +---{@link Ext.direct.RemotingProvider RemotingProvider}
82424  * </code></pre>
82425  * @abstract
82426  */
82427 Ext.define('Ext.direct.Provider', {
82428
82429     /* Begin Definitions */
82430
82431    alias: 'direct.provider',
82432
82433     mixins: {
82434         observable: 'Ext.util.Observable'
82435     },
82436
82437     /* End Definitions */
82438
82439    /**
82440      * @cfg {String} id
82441      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
82442      * You should assign an id if you need to be able to access the provider later and you do
82443      * not have an object reference available, for example:
82444      * <pre><code>
82445 Ext.direct.Manager.addProvider({
82446     type: 'polling',
82447     url:  'php/poll.php',
82448     id:   'poll-provider'
82449 });
82450 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
82451 p.disconnect();
82452      * </code></pre>
82453      */
82454
82455     constructor : function(config){
82456         var me = this;
82457
82458         Ext.apply(me, config);
82459         me.addEvents(
82460             /**
82461              * @event connect
82462              * Fires when the Provider connects to the server-side
82463              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
82464              */
82465             'connect',
82466             /**
82467              * @event disconnect
82468              * Fires when the Provider disconnects from the server-side
82469              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
82470              */
82471             'disconnect',
82472             /**
82473              * @event data
82474              * Fires when the Provider receives data from the server-side
82475              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
82476              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
82477              */
82478             'data',
82479             /**
82480              * @event exception
82481              * Fires when the Provider receives an exception from the server-side
82482              */
82483             'exception'
82484         );
82485         me.mixins.observable.constructor.call(me, config);
82486     },
82487
82488     /**
82489      * Returns whether or not the server-side is currently connected.
82490      * Abstract method for subclasses to implement.
82491      */
82492     isConnected: function(){
82493         return false;
82494     },
82495
82496     /**
82497      * Abstract methods for subclasses to implement.
82498      * @method
82499      */
82500     connect: Ext.emptyFn,
82501
82502     /**
82503      * Abstract methods for subclasses to implement.
82504      * @method
82505      */
82506     disconnect: Ext.emptyFn
82507 });
82508
82509 /**
82510  * @class Ext.direct.JsonProvider
82511  * @extends Ext.direct.Provider
82512
82513 A base provider for communicating using JSON. This is an abstract class
82514 and should not be instanced directly.
82515
82516  * @markdown
82517  * @abstract
82518  */
82519
82520 Ext.define('Ext.direct.JsonProvider', {
82521
82522     /* Begin Definitions */
82523
82524     extend: 'Ext.direct.Provider',
82525
82526     alias: 'direct.jsonprovider',
82527
82528     uses: ['Ext.direct.ExceptionEvent'],
82529
82530     /* End Definitions */
82531
82532    /**
82533     * Parse the JSON response
82534     * @private
82535     * @param {Object} response The XHR response object
82536     * @return {Object} The data in the response.
82537     */
82538    parseResponse: function(response){
82539         if (!Ext.isEmpty(response.responseText)) {
82540             if (Ext.isObject(response.responseText)) {
82541                 return response.responseText;
82542             }
82543             return Ext.decode(response.responseText);
82544         }
82545         return null;
82546     },
82547
82548     /**
82549      * Creates a set of events based on the XHR response
82550      * @private
82551      * @param {Object} response The XHR response
82552      * @return {Ext.direct.Event[]} An array of Ext.direct.Event
82553      */
82554     createEvents: function(response){
82555         var data = null,
82556             events = [],
82557             event,
82558             i = 0,
82559             len;
82560
82561         try{
82562             data = this.parseResponse(response);
82563         } catch(e) {
82564             event = Ext.create('Ext.direct.ExceptionEvent', {
82565                 data: e,
82566                 xhr: response,
82567                 code: Ext.direct.Manager.self.exceptions.PARSE,
82568                 message: 'Error parsing json response: \n\n ' + data
82569             });
82570             return [event];
82571         }
82572
82573         if (Ext.isArray(data)) {
82574             for (len = data.length; i < len; ++i) {
82575                 events.push(this.createEvent(data[i]));
82576             }
82577         } else {
82578             events.push(this.createEvent(data));
82579         }
82580         return events;
82581     },
82582
82583     /**
82584      * Create an event from a response object
82585      * @param {Object} response The XHR response object
82586      * @return {Ext.direct.Event} The event
82587      */
82588     createEvent: function(response){
82589         return Ext.create('direct.' + response.type, response);
82590     }
82591 });
82592 /**
82593  * @class Ext.direct.PollingProvider
82594  * @extends Ext.direct.JsonProvider
82595  *
82596  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
82597  * The initial request for data originates from the client, and then is responded to by the
82598  * server.</p>
82599  * 
82600  * <p>All configurations for the PollingProvider should be generated by the server-side
82601  * API portion of the Ext.Direct stack.</p>
82602  *
82603  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
82604  * specifying <tt>type = 'polling'</tt>.  For example:</p>
82605  * <pre><code>
82606 var pollA = new Ext.direct.PollingProvider({
82607     type:'polling',
82608     url: 'php/pollA.php',
82609 });
82610 Ext.direct.Manager.addProvider(pollA);
82611 pollA.disconnect();
82612
82613 Ext.direct.Manager.addProvider(
82614     {
82615         type:'polling',
82616         url: 'php/pollB.php',
82617         id: 'pollB-provider'
82618     }
82619 );
82620 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
82621  * </code></pre>
82622  */
82623 Ext.define('Ext.direct.PollingProvider', {
82624     
82625     /* Begin Definitions */
82626     
82627     extend: 'Ext.direct.JsonProvider',
82628     
82629     alias: 'direct.pollingprovider',
82630     
82631     uses: ['Ext.direct.ExceptionEvent'],
82632     
82633     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
82634     
82635     /* End Definitions */
82636     
82637     /**
82638      * @cfg {Number} interval
82639      * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
82640      */
82641     interval: 3000,
82642
82643     /**
82644      * @cfg {Object} baseParams
82645      * An object containing properties which are to be sent as parameters on every polling request
82646      */
82647     
82648     /**
82649      * @cfg {String/Function} url
82650      * The url which the PollingProvider should contact with each request. This can also be
82651      * an imported Ext.Direct method which will accept the baseParams as its only argument.
82652      */
82653
82654     // private
82655     constructor : function(config){
82656         this.callParent(arguments);
82657         this.addEvents(
82658             /**
82659              * @event beforepoll
82660              * Fired immediately before a poll takes place, an event handler can return false
82661              * in order to cancel the poll.
82662              * @param {Ext.direct.PollingProvider} this
82663              */
82664             'beforepoll',            
82665             /**
82666              * @event poll
82667              * This event has not yet been implemented.
82668              * @param {Ext.direct.PollingProvider} this
82669              */
82670             'poll'
82671         );
82672     },
82673
82674     // inherited
82675     isConnected: function(){
82676         return !!this.pollTask;
82677     },
82678
82679     /**
82680      * Connect to the server-side and begin the polling process. To handle each
82681      * response subscribe to the data event.
82682      */
82683     connect: function(){
82684         var me = this, url = me.url;
82685         
82686         if (url && !me.pollTask) {
82687             me.pollTask = Ext.TaskManager.start({
82688                 run: function(){
82689                     if (me.fireEvent('beforepoll', me) !== false) {
82690                         if (Ext.isFunction(url)) {
82691                             url(me.baseParams);
82692                         } else {
82693                             Ext.Ajax.request({
82694                                 url: url,
82695                                 callback: me.onData,
82696                                 scope: me,
82697                                 params: me.baseParams
82698                             });
82699                         }
82700                     }
82701                 },
82702                 interval: me.interval,
82703                 scope: me
82704             });
82705             me.fireEvent('connect', me);
82706         } else if (!url) {
82707             Ext.Error.raise('Error initializing PollingProvider, no url configured.');
82708         }
82709     },
82710
82711     /**
82712      * Disconnect from the server-side and stop the polling process. The disconnect
82713      * event will be fired on a successful disconnect.
82714      */
82715     disconnect: function(){
82716         var me = this;
82717         
82718         if (me.pollTask) {
82719             Ext.TaskManager.stop(me.pollTask);
82720             delete me.pollTask;
82721             me.fireEvent('disconnect', me);
82722         }
82723     },
82724
82725     // private
82726     onData: function(opt, success, response){
82727         var me = this, 
82728             i = 0, 
82729             len,
82730             events;
82731         
82732         if (success) {
82733             events = me.createEvents(response);
82734             for (len = events.length; i < len; ++i) {
82735                 me.fireEvent('data', me, events[i]);
82736             }
82737         } else {
82738             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
82739                 data: null,
82740                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82741                 message: 'Unable to connect to the server.',
82742                 xhr: response
82743             }));
82744         }
82745     }
82746 });
82747 /**
82748  * Small utility class used internally to represent a Direct method.
82749  * @class Ext.direct.RemotingMethod
82750  * @ignore
82751  */
82752 Ext.define('Ext.direct.RemotingMethod', {
82753
82754     constructor: function(config){
82755         var me = this,
82756             params = Ext.isDefined(config.params) ? config.params : config.len,
82757             name;
82758
82759         me.name = config.name;
82760         me.formHandler = config.formHandler;
82761         if (Ext.isNumber(params)) {
82762             // given only the number of parameters
82763             me.len = params;
82764             me.ordered = true;
82765         } else {
82766             /*
82767              * Given an array of either
82768              * a) String
82769              * b) Objects with a name property. We may want to encode extra info in here later
82770              */
82771             me.params = [];
82772             Ext.each(params, function(param){
82773                 name = Ext.isObject(param) ? param.name : param;
82774                 me.params.push(name);
82775             });
82776         }
82777     },
82778
82779     /**
82780      * Takes the arguments for the Direct function and splits the arguments
82781      * from the scope and the callback.
82782      * @param {Array} args The arguments passed to the direct call
82783      * @return {Object} An object with 3 properties, args, callback & scope.
82784      */
82785     getCallData: function(args){
82786         var me = this,
82787             data = null,
82788             len  = me.len,
82789             params = me.params,
82790             callback,
82791             scope,
82792             name;
82793
82794         if (me.ordered) {
82795             callback = args[len];
82796             scope = args[len + 1];
82797             if (len !== 0) {
82798                 data = args.slice(0, len);
82799             }
82800         } else {
82801             data = Ext.apply({}, args[0]);
82802             callback = args[1];
82803             scope = args[2];
82804
82805             // filter out any non-existent properties
82806             for (name in data) {
82807                 if (data.hasOwnProperty(name)) {
82808                     if (!Ext.Array.contains(params, name)) {
82809                         delete data[name];
82810                     }
82811                 }
82812             }
82813         }
82814
82815         return {
82816             data: data,
82817             callback: callback,
82818             scope: scope
82819         };
82820     }
82821 });
82822
82823 /**
82824  * Supporting Class for Ext.Direct (not intended to be used directly).
82825  */
82826 Ext.define('Ext.direct.Transaction', {
82827     
82828     /* Begin Definitions */
82829    
82830     alias: 'direct.transaction',
82831     alternateClassName: 'Ext.Direct.Transaction',
82832    
82833     statics: {
82834         TRANSACTION_ID: 0
82835     },
82836    
82837     /* End Definitions */
82838
82839     /**
82840      * Creates new Transaction.
82841      * @param {Object} [config] Config object.
82842      */
82843     constructor: function(config){
82844         var me = this;
82845         
82846         Ext.apply(me, config);
82847         me.id = ++me.self.TRANSACTION_ID;
82848         me.retryCount = 0;
82849     },
82850    
82851     send: function(){
82852          this.provider.queueTransaction(this);
82853     },
82854
82855     retry: function(){
82856         this.retryCount++;
82857         this.send();
82858     },
82859
82860     getProvider: function(){
82861         return this.provider;
82862     }
82863 });
82864
82865 /**
82866  * @class Ext.direct.RemotingProvider
82867  * @extends Ext.direct.JsonProvider
82868  * 
82869  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
82870  * server side methods on the client (a remote procedure call (RPC) type of
82871  * connection where the client can initiate a procedure on the server).</p>
82872  * 
82873  * <p>This allows for code to be organized in a fashion that is maintainable,
82874  * while providing a clear path between client and server, something that is
82875  * not always apparent when using URLs.</p>
82876  * 
82877  * <p>To accomplish this the server-side needs to describe what classes and methods
82878  * are available on the client-side. This configuration will typically be
82879  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
82880  */
82881 Ext.define('Ext.direct.RemotingProvider', {
82882     
82883     /* Begin Definitions */
82884    
82885     alias: 'direct.remotingprovider',
82886     
82887     extend: 'Ext.direct.JsonProvider', 
82888     
82889     requires: [
82890         'Ext.util.MixedCollection', 
82891         'Ext.util.DelayedTask', 
82892         'Ext.direct.Transaction',
82893         'Ext.direct.RemotingMethod'
82894     ],
82895    
82896     /* End Definitions */
82897    
82898    /**
82899      * @cfg {Object} actions
82900      * Object literal defining the server side actions and methods. For example, if
82901      * the Provider is configured with:
82902      * <pre><code>
82903 "actions":{ // each property within the 'actions' object represents a server side Class 
82904     "TestAction":[ // array of methods within each server side Class to be   
82905     {              // stubbed out on client
82906         "name":"doEcho", 
82907         "len":1            
82908     },{
82909         "name":"multiply",// name of method
82910         "len":2           // The number of parameters that will be used to create an
82911                           // array of data to send to the server side function.
82912                           // Ensure the server sends back a Number, not a String. 
82913     },{
82914         "name":"doForm",
82915         "formHandler":true, // direct the client to use specialized form handling method 
82916         "len":1
82917     }]
82918 }
82919      * </code></pre>
82920      * <p>Note that a Store is not required, a server method can be called at any time.
82921      * In the following example a <b>client side</b> handler is used to call the
82922      * server side method "multiply" in the server-side "TestAction" Class:</p>
82923      * <pre><code>
82924 TestAction.multiply(
82925     2, 4, // pass two arguments to server, so specify len=2
82926     // callback function after the server is called
82927     // result: the result returned by the server
82928     //      e: Ext.direct.RemotingEvent object
82929     function(result, e){
82930         var t = e.getTransaction();
82931         var action = t.action; // server side Class called
82932         var method = t.method; // server side method called
82933         if(e.status){
82934             var answer = Ext.encode(result); // 8
82935     
82936         }else{
82937             var msg = e.message; // failure message
82938         }
82939     }
82940 );
82941      * </code></pre>
82942      * In the example above, the server side "multiply" function will be passed two
82943      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
82944      * available as the <tt>result</tt> in the example above. 
82945      */
82946     
82947     /**
82948      * @cfg {String/Object} namespace
82949      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
82950      * Explicitly specify the namespace Object, or specify a String to have a
82951      * {@link Ext#namespace namespace created} implicitly.
82952      */
82953     
82954     /**
82955      * @cfg {String} url
82956      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
82957      */
82958     
82959     /**
82960      * @cfg {String} enableUrlEncode
82961      * Specify which param will hold the arguments for the method.
82962      * Defaults to <tt>'data'</tt>.
82963      */
82964     
82965     /**
82966      * @cfg {Number/Boolean} enableBuffer
82967      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
82968      * calls. If a number is specified this is the amount of time in milliseconds
82969      * to wait before sending a batched request.</p>
82970      * <br><p>Calls which are received within the specified timeframe will be
82971      * concatenated together and sent in a single request, optimizing the
82972      * application by reducing the amount of round trips that have to be made
82973      * to the server.</p>
82974      */
82975     enableBuffer: 10,
82976     
82977     /**
82978      * @cfg {Number} maxRetries
82979      * Number of times to re-attempt delivery on failure of a call.
82980      */
82981     maxRetries: 1,
82982     
82983     /**
82984      * @cfg {Number} timeout
82985      * The timeout to use for each request.
82986      */
82987     timeout: undefined,
82988     
82989     constructor : function(config){
82990         var me = this;
82991         me.callParent(arguments);
82992         me.addEvents(
82993             /**
82994              * @event beforecall
82995              * Fires immediately before the client-side sends off the RPC call.
82996              * By returning false from an event handler you can prevent the call from
82997              * executing.
82998              * @param {Ext.direct.RemotingProvider} provider
82999              * @param {Ext.direct.Transaction} transaction
83000              * @param {Object} meta The meta data
83001              */            
83002             'beforecall',            
83003             /**
83004              * @event call
83005              * Fires immediately after the request to the server-side is sent. This does
83006              * NOT fire after the response has come back from the call.
83007              * @param {Ext.direct.RemotingProvider} provider
83008              * @param {Ext.direct.Transaction} transaction
83009              * @param {Object} meta The meta data
83010              */            
83011             'call'
83012         );
83013         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
83014         me.transactions = Ext.create('Ext.util.MixedCollection');
83015         me.callBuffer = [];
83016     },
83017     
83018     /**
83019      * Initialize the API
83020      * @private
83021      */
83022     initAPI : function(){
83023         var actions = this.actions,
83024             namespace = this.namespace,
83025             action,
83026             cls,
83027             methods,
83028             i,
83029             len,
83030             method;
83031             
83032         for (action in actions) {
83033             cls = namespace[action];
83034             if (!cls) {
83035                 cls = namespace[action] = {};
83036             }
83037             methods = actions[action];
83038             
83039             for (i = 0, len = methods.length; i < len; ++i) {
83040                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
83041                 cls[method.name] = this.createHandler(action, method);
83042             }
83043         }
83044     },
83045     
83046     /**
83047      * Create a handler function for a direct call.
83048      * @private
83049      * @param {String} action The action the call is for
83050      * @param {Object} method The details of the method
83051      * @return {Function} A JS function that will kick off the call
83052      */
83053     createHandler : function(action, method){
83054         var me = this,
83055             handler;
83056         
83057         if (!method.formHandler) {
83058             handler = function(){
83059                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
83060             };
83061         } else {
83062             handler = function(form, callback, scope){
83063                 me.configureFormRequest(action, method, form, callback, scope);
83064             };
83065         }
83066         handler.directCfg = {
83067             action: action,
83068             method: method
83069         };
83070         return handler;
83071     },
83072     
83073     // inherit docs
83074     isConnected: function(){
83075         return !!this.connected;
83076     },
83077
83078     // inherit docs
83079     connect: function(){
83080         var me = this;
83081         
83082         if (me.url) {
83083             me.initAPI();
83084             me.connected = true;
83085             me.fireEvent('connect', me);
83086         } else if(!me.url) {
83087             Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
83088         }
83089     },
83090
83091     // inherit docs
83092     disconnect: function(){
83093         var me = this;
83094         
83095         if (me.connected) {
83096             me.connected = false;
83097             me.fireEvent('disconnect', me);
83098         }
83099     },
83100     
83101     /**
83102      * Run any callbacks related to the transaction.
83103      * @private
83104      * @param {Ext.direct.Transaction} transaction The transaction
83105      * @param {Ext.direct.Event} event The event
83106      */
83107     runCallback: function(transaction, event){
83108         var funcName = event.status ? 'success' : 'failure',
83109             callback,
83110             result;
83111         
83112         if (transaction && transaction.callback) {
83113             callback = transaction.callback;
83114             result = Ext.isDefined(event.result) ? event.result : event.data;
83115         
83116             if (Ext.isFunction(callback)) {
83117                 callback(result, event);
83118             } else {
83119                 Ext.callback(callback[funcName], callback.scope, [result, event]);
83120                 Ext.callback(callback.callback, callback.scope, [result, event]);
83121             }
83122         }
83123     },
83124     
83125     /**
83126      * React to the ajax request being completed
83127      * @private
83128      */
83129     onData: function(options, success, response){
83130         var me = this,
83131             i = 0,
83132             len,
83133             events,
83134             event,
83135             transaction,
83136             transactions;
83137             
83138         if (success) {
83139             events = me.createEvents(response);
83140             for (len = events.length; i < len; ++i) {
83141                 event = events[i];
83142                 transaction = me.getTransaction(event);
83143                 me.fireEvent('data', me, event);
83144                 if (transaction) {
83145                     me.runCallback(transaction, event, true);
83146                     Ext.direct.Manager.removeTransaction(transaction);
83147                 }
83148             }
83149         } else {
83150             transactions = [].concat(options.transaction);
83151             for (len = transactions.length; i < len; ++i) {
83152                 transaction = me.getTransaction(transactions[i]);
83153                 if (transaction && transaction.retryCount < me.maxRetries) {
83154                     transaction.retry();
83155                 } else {
83156                     event = Ext.create('Ext.direct.ExceptionEvent', {
83157                         data: null,
83158                         transaction: transaction,
83159                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
83160                         message: 'Unable to connect to the server.',
83161                         xhr: response
83162                     });
83163                     me.fireEvent('data', me, event);
83164                     if (transaction) {
83165                         me.runCallback(transaction, event, false);
83166                         Ext.direct.Manager.removeTransaction(transaction);
83167                     }
83168                 }
83169             }
83170         }
83171     },
83172     
83173     /**
83174      * Get transaction from XHR options
83175      * @private
83176      * @param {Object} options The options sent to the Ajax request
83177      * @return {Ext.direct.Transaction} The transaction, null if not found
83178      */
83179     getTransaction: function(options){
83180         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
83181     },
83182     
83183     /**
83184      * Configure a direct request
83185      * @private
83186      * @param {String} action The action being executed
83187      * @param {Object} method The being executed
83188      */
83189     configureRequest: function(action, method, args){
83190         var me = this,
83191             callData = method.getCallData(args),
83192             data = callData.data, 
83193             callback = callData.callback, 
83194             scope = callData.scope,
83195             transaction;
83196
83197         transaction = Ext.create('Ext.direct.Transaction', {
83198             provider: me,
83199             args: args,
83200             action: action,
83201             method: method.name,
83202             data: data,
83203             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
83204         });
83205
83206         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
83207             Ext.direct.Manager.addTransaction(transaction);
83208             me.queueTransaction(transaction);
83209             me.fireEvent('call', me, transaction, method);
83210         }
83211     },
83212     
83213     /**
83214      * Gets the Ajax call info for a transaction
83215      * @private
83216      * @param {Ext.direct.Transaction} transaction The transaction
83217      * @return {Object} The call params
83218      */
83219     getCallData: function(transaction){
83220         return {
83221             action: transaction.action,
83222             method: transaction.method,
83223             data: transaction.data,
83224             type: 'rpc',
83225             tid: transaction.id
83226         };
83227     },
83228     
83229     /**
83230      * Sends a request to the server
83231      * @private
83232      * @param {Object/Array} data The data to send
83233      */
83234     sendRequest : function(data){
83235         var me = this,
83236             request = {
83237                 url: me.url,
83238                 callback: me.onData,
83239                 scope: me,
83240                 transaction: data,
83241                 timeout: me.timeout
83242             }, callData,
83243             enableUrlEncode = me.enableUrlEncode,
83244             i = 0,
83245             len,
83246             params;
83247             
83248
83249         if (Ext.isArray(data)) {
83250             callData = [];
83251             for (len = data.length; i < len; ++i) {
83252                 callData.push(me.getCallData(data[i]));
83253             }
83254         } else {
83255             callData = me.getCallData(data);
83256         }
83257
83258         if (enableUrlEncode) {
83259             params = {};
83260             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
83261             request.params = params;
83262         } else {
83263             request.jsonData = callData;
83264         }
83265         Ext.Ajax.request(request);
83266     },
83267     
83268     /**
83269      * Add a new transaction to the queue
83270      * @private
83271      * @param {Ext.direct.Transaction} transaction The transaction
83272      */
83273     queueTransaction: function(transaction){
83274         var me = this,
83275             enableBuffer = me.enableBuffer;
83276         
83277         if (transaction.form) {
83278             me.sendFormRequest(transaction);
83279             return;
83280         }
83281         
83282         me.callBuffer.push(transaction);
83283         if (enableBuffer) {
83284             if (!me.callTask) {
83285                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
83286             }
83287             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
83288         } else {
83289             me.combineAndSend();
83290         }
83291     },
83292     
83293     /**
83294      * Combine any buffered requests and send them off
83295      * @private
83296      */
83297     combineAndSend : function(){
83298         var buffer = this.callBuffer,
83299             len = buffer.length;
83300             
83301         if (len > 0) {
83302             this.sendRequest(len == 1 ? buffer[0] : buffer);
83303             this.callBuffer = [];
83304         }
83305     },
83306     
83307     /**
83308      * Configure a form submission request
83309      * @private
83310      * @param {String} action The action being executed
83311      * @param {Object} method The method being executed
83312      * @param {HTMLElement} form The form being submitted
83313      * @param {Function} callback (optional) A callback to run after the form submits
83314      * @param {Object} scope (optional) A scope to execute the callback in
83315      */
83316     configureFormRequest : function(action, method, form, callback, scope){
83317         var me = this,
83318             transaction = Ext.create('Ext.direct.Transaction', {
83319                 provider: me,
83320                 action: action,
83321                 method: method.name,
83322                 args: [form, callback, scope],
83323                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
83324                 isForm: true
83325             }),
83326             isUpload,
83327             params;
83328
83329         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
83330             Ext.direct.Manager.addTransaction(transaction);
83331             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
83332             
83333             params = {
83334                 extTID: transaction.id,
83335                 extAction: action,
83336                 extMethod: method.name,
83337                 extType: 'rpc',
83338                 extUpload: String(isUpload)
83339             };
83340             
83341             // change made from typeof callback check to callback.params
83342             // to support addl param passing in DirectSubmit EAC 6/2
83343             Ext.apply(transaction, {
83344                 form: Ext.getDom(form),
83345                 isUpload: isUpload,
83346                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
83347             });
83348             me.fireEvent('call', me, transaction, method);
83349             me.sendFormRequest(transaction);
83350         }
83351     },
83352     
83353     /**
83354      * Sends a form request
83355      * @private
83356      * @param {Ext.direct.Transaction} transaction The transaction to send
83357      */
83358     sendFormRequest: function(transaction){
83359         Ext.Ajax.request({
83360             url: this.url,
83361             params: transaction.params,
83362             callback: this.onData,
83363             scope: this,
83364             form: transaction.form,
83365             isUpload: transaction.isUpload,
83366             transaction: transaction
83367         });
83368     }
83369     
83370 });
83371
83372 /*
83373  * @class Ext.draw.Matrix
83374  * @private
83375  */
83376 Ext.define('Ext.draw.Matrix', {
83377
83378     /* Begin Definitions */
83379
83380     requires: ['Ext.draw.Draw'],
83381
83382     /* End Definitions */
83383
83384     constructor: function(a, b, c, d, e, f) {
83385         if (a != null) {
83386             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
83387         }
83388         else {
83389             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
83390         }
83391     },
83392
83393     add: function(a, b, c, d, e, f) {
83394         var me = this,
83395             out = [[], [], []],
83396             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
83397             x,
83398             y,
83399             z,
83400             res;
83401
83402         for (x = 0; x < 3; x++) {
83403             for (y = 0; y < 3; y++) {
83404                 res = 0;
83405                 for (z = 0; z < 3; z++) {
83406                     res += me.matrix[x][z] * matrix[z][y];
83407                 }
83408                 out[x][y] = res;
83409             }
83410         }
83411         me.matrix = out;
83412     },
83413
83414     prepend: function(a, b, c, d, e, f) {
83415         var me = this,
83416             out = [[], [], []],
83417             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
83418             x,
83419             y,
83420             z,
83421             res;
83422
83423         for (x = 0; x < 3; x++) {
83424             for (y = 0; y < 3; y++) {
83425                 res = 0;
83426                 for (z = 0; z < 3; z++) {
83427                     res += matrix[x][z] * me.matrix[z][y];
83428                 }
83429                 out[x][y] = res;
83430             }
83431         }
83432         me.matrix = out;
83433     },
83434
83435     invert: function() {
83436         var matrix = this.matrix,
83437             a = matrix[0][0],
83438             b = matrix[1][0],
83439             c = matrix[0][1],
83440             d = matrix[1][1],
83441             e = matrix[0][2],
83442             f = matrix[1][2],
83443             x = a * d - b * c;
83444         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
83445     },
83446
83447     clone: function() {
83448         var matrix = this.matrix,
83449             a = matrix[0][0],
83450             b = matrix[1][0],
83451             c = matrix[0][1],
83452             d = matrix[1][1],
83453             e = matrix[0][2],
83454             f = matrix[1][2];
83455         return new Ext.draw.Matrix(a, b, c, d, e, f);
83456     },
83457
83458     translate: function(x, y) {
83459         this.prepend(1, 0, 0, 1, x, y);
83460     },
83461
83462     scale: function(x, y, cx, cy) {
83463         var me = this;
83464         if (y == null) {
83465             y = x;
83466         }
83467         me.add(1, 0, 0, 1, cx, cy);
83468         me.add(x, 0, 0, y, 0, 0);
83469         me.add(1, 0, 0, 1, -cx, -cy);
83470     },
83471
83472     rotate: function(a, x, y) {
83473         a = Ext.draw.Draw.rad(a);
83474         var me = this,
83475             cos = +Math.cos(a).toFixed(9),
83476             sin = +Math.sin(a).toFixed(9);
83477         me.add(cos, sin, -sin, cos, x, y);
83478         me.add(1, 0, 0, 1, -x, -y);
83479     },
83480
83481     x: function(x, y) {
83482         var matrix = this.matrix;
83483         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
83484     },
83485
83486     y: function(x, y) {
83487         var matrix = this.matrix;
83488         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
83489     },
83490
83491     get: function(i, j) {
83492         return + this.matrix[i][j].toFixed(4);
83493     },
83494
83495     toString: function() {
83496         var me = this;
83497         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
83498     },
83499
83500     toSvg: function() {
83501         var me = this;
83502         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() + ")";
83503     },
83504
83505     toFilter: function() {
83506         var me = this;
83507         return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',FilterType=bilinear,M11=" + me.get(0, 0) +
83508             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
83509             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
83510     },
83511
83512     offset: function() {
83513         var matrix = this.matrix;
83514         return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
83515     },
83516
83517     // Split matrix into Translate Scale, Shear, and Rotate
83518     split: function () {
83519         function norm(a) {
83520             return a[0] * a[0] + a[1] * a[1];
83521         }
83522         function normalize(a) {
83523             var mag = Math.sqrt(norm(a));
83524             a[0] /= mag;
83525             a[1] /= mag;
83526         }
83527         var matrix = this.matrix,
83528             out = {
83529                 translateX: matrix[0][2],
83530                 translateY: matrix[1][2]
83531             },
83532             row;
83533
83534         // scale and shear
83535         row = [[matrix[0][0], matrix[0][1]], [matrix[1][0], matrix[1][1]]];
83536         out.scaleX = Math.sqrt(norm(row[0]));
83537         normalize(row[0]);
83538
83539         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
83540         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
83541
83542         out.scaleY = Math.sqrt(norm(row[1]));
83543         normalize(row[1]);
83544         out.shear /= out.scaleY;
83545
83546         // rotation
83547         out.rotate = Math.asin(-row[0][1]);
83548
83549         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
83550
83551         return out;
83552     }
83553 });
83554
83555 // private - DD implementation for Panels
83556 Ext.define('Ext.draw.SpriteDD', {
83557     extend: 'Ext.dd.DragSource',
83558
83559     constructor : function(sprite, cfg){
83560         var me = this,
83561             el = sprite.el;
83562         me.sprite = sprite;
83563         me.el = el;
83564         me.dragData = {el: el, sprite: sprite};
83565         me.callParent([el, cfg]);
83566         me.sprite.setStyle('cursor', 'move');
83567     },
83568
83569     showFrame: Ext.emptyFn,
83570     createFrame : Ext.emptyFn,
83571
83572     getDragEl : function(e){
83573         return this.el;
83574     },
83575     
83576     getRegion: function() {
83577         var me = this,
83578             el = me.el,
83579             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
83580         
83581         sprite = me.sprite;
83582         bbox = sprite.getBBox();
83583         
83584         try {
83585             pos = Ext.Element.getXY(el);
83586         } catch (e) { }
83587
83588         if (!pos) {
83589             return null;
83590         }
83591
83592         x1 = pos[0];
83593         x2 = x1 + bbox.width;
83594         y1 = pos[1];
83595         y2 = y1 + bbox.height;
83596         
83597         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
83598     },
83599
83600     /*
83601       TODO(nico): Cumulative translations in VML are handled
83602       differently than in SVG. While in SVG we specify the translation
83603       relative to the original x, y position attributes, in VML the translation
83604       is a delta between the last position of the object (modified by the last
83605       translation) and the new one.
83606       
83607       In VML the translation alters the position
83608       of the object, we should change that or alter the SVG impl.
83609     */
83610      
83611     startDrag: function(x, y) {
83612         var me = this,
83613             attr = me.sprite.attr;
83614         me.prev = me.sprite.surface.transformToViewBox(x, y);
83615     },
83616
83617     onDrag: function(e) {
83618         var xy = e.getXY(),
83619             me = this,
83620             sprite = me.sprite,
83621             attr = sprite.attr, dx, dy;
83622         xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
83623         dx = xy[0] - me.prev[0];
83624         dy = xy[1] - me.prev[1];
83625         sprite.setAttributes({
83626             translate: {
83627                 x: attr.translation.x + dx,
83628                 y: attr.translation.y + dy
83629             }
83630         }, true);
83631         me.prev = xy;
83632     },
83633
83634     setDragElPos: function () {
83635         // Disable automatic DOM move in DD that spoils layout of VML engine.
83636         return false;
83637     }
83638 });
83639 /**
83640  * A Sprite is an object rendered in a Drawing surface.
83641  *
83642  * # Translation
83643  *
83644  * For translate, the configuration object contains x and y attributes that indicate where to
83645  * translate the object. For example:
83646  *
83647  *     sprite.setAttributes({
83648  *       translate: {
83649  *        x: 10,
83650  *        y: 10
83651  *       }
83652  *     }, true);
83653  *
83654  *
83655  * # Rotation
83656  *
83657  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
83658  * and a `degrees` attribute that specifies the rotation in degrees. For example:
83659  *
83660  *     sprite.setAttributes({
83661  *       rotate: {
83662  *        degrees: 90
83663  *       }
83664  *     }, true);
83665  *
83666  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
83667  *
83668  *     sprite.setAttributes({
83669  *       rotate: {
83670  *        x: 0,
83671  *        y: 0,
83672  *        degrees: 90
83673  *       }
83674  *     }, true);
83675  *
83676  * will create a rotation around the `(0, 0)` axis.
83677  *
83678  *
83679  * # Scaling
83680  *
83681  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
83682  *
83683  *     sprite.setAttributes({
83684  *       scale: {
83685  *        x: 10,
83686  *        y: 3
83687  *       }
83688  *     }, true);
83689  *
83690  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
83691  *
83692  *     sprite.setAttributes({
83693  *       scale: {
83694  *        cx: 0,
83695  *        cy: 0,
83696  *        x: 10,
83697  *        y: 3
83698  *       }
83699  *     }, true);
83700  *
83701  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
83702  *
83703  *
83704  * # Creating and adding a Sprite to a Surface
83705  *
83706  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
83707  *
83708  *     var drawComponent = Ext.create('Ext.draw.Component', options here...);
83709  *
83710  *     var sprite = Ext.create('Ext.draw.Sprite', {
83711  *         type: 'circle',
83712  *         fill: '#ff0',
83713  *         surface: drawComponent.surface,
83714  *         radius: 5
83715  *     });
83716  *
83717  * Sprites can also be added to the surface as a configuration object:
83718  *
83719  *     var sprite = drawComponent.surface.add({
83720  *         type: 'circle',
83721  *         fill: '#ff0',
83722  *         radius: 5
83723  *     });
83724  *
83725  * In order to properly apply properties and render the sprite we have to
83726  * `show` the sprite setting the option `redraw` to `true`:
83727  *
83728  *     sprite.show(true);
83729  *
83730  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
83731  * add method to append a new sprite to the canvas. For example:
83732  *
83733  *     drawComponent.surface.add({
83734  *         type: 'circle',
83735  *         fill: '#ffc',
83736  *         radius: 100,
83737  *         x: 100,
83738  *         y: 100
83739  *     });
83740  */
83741 Ext.define('Ext.draw.Sprite', {
83742
83743     /* Begin Definitions */
83744
83745     mixins: {
83746         observable: 'Ext.util.Observable',
83747         animate: 'Ext.util.Animate'
83748     },
83749
83750     requires: ['Ext.draw.SpriteDD'],
83751
83752     /* End Definitions */
83753
83754     /**
83755      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
83756      */
83757
83758     /**
83759      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
83760      */
83761
83762     /**
83763      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
83764      */
83765
83766     /**
83767      * @cfg {Number} size Used in square sprites, the dimension of the square
83768      */
83769
83770     /**
83771      * @cfg {Number} radius Used in circle sprites, the radius of the circle
83772      */
83773
83774     /**
83775      * @cfg {Number} x The position along the x-axis
83776      */
83777
83778     /**
83779      * @cfg {Number} y The position along the y-axis
83780      */
83781
83782     /**
83783      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
83784      */
83785
83786     /**
83787      * @cfg {Number} opacity The opacity of the sprite
83788      */
83789
83790     /**
83791      * @cfg {String} fill The fill color
83792      */
83793
83794     /**
83795      * @cfg {String} stroke The stroke color
83796      */
83797
83798     /**
83799      * @cfg {Number} stroke-width The width of the stroke
83800      */
83801
83802     /**
83803      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
83804      */
83805
83806     /**
83807      * @cfg {String} text Used with text type sprites. The text itself
83808      */
83809
83810     /**
83811      * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
83812      * {@link Ext.draw.Surface}
83813      */
83814
83815     /**
83816      * @cfg {Boolean} draggable True to make the sprite draggable.
83817      */
83818
83819     dirty: false,
83820     dirtyHidden: false,
83821     dirtyTransform: false,
83822     dirtyPath: true,
83823     dirtyFont: true,
83824     zIndexDirty: true,
83825     isSprite: true,
83826     zIndex: 0,
83827     fontProperties: [
83828         'font',
83829         'font-size',
83830         'font-weight',
83831         'font-style',
83832         'font-family',
83833         'text-anchor',
83834         'text'
83835     ],
83836     pathProperties: [
83837         'x',
83838         'y',
83839         'd',
83840         'path',
83841         'height',
83842         'width',
83843         'radius',
83844         'r',
83845         'rx',
83846         'ry',
83847         'cx',
83848         'cy'
83849     ],
83850     constructor: function(config) {
83851         var me = this;
83852         config = config || {};
83853         me.id = Ext.id(null, 'ext-sprite-');
83854         me.transformations = [];
83855         Ext.copyTo(this, config, 'surface,group,type,draggable');
83856         //attribute bucket
83857         me.bbox = {};
83858         me.attr = {
83859             zIndex: 0,
83860             translation: {
83861                 x: null,
83862                 y: null
83863             },
83864             rotation: {
83865                 degrees: null,
83866                 x: null,
83867                 y: null
83868             },
83869             scaling: {
83870                 x: null,
83871                 y: null,
83872                 cx: null,
83873                 cy: null
83874             }
83875         };
83876         //delete not bucket attributes
83877         delete config.surface;
83878         delete config.group;
83879         delete config.type;
83880         delete config.draggable;
83881         me.setAttributes(config);
83882         me.addEvents(
83883             'beforedestroy',
83884             'destroy',
83885             'render',
83886             'mousedown',
83887             'mouseup',
83888             'mouseover',
83889             'mouseout',
83890             'mousemove',
83891             'click'
83892         );
83893         me.mixins.observable.constructor.apply(this, arguments);
83894     },
83895
83896     /**
83897      * @property {Ext.dd.DragSource} dd
83898      * If this Sprite is configured {@link #draggable}, this property will contain
83899      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
83900      *
83901      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
83902      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
83903      */
83904
83905     initDraggable: function() {
83906         var me = this;
83907         me.draggable = true;
83908         //create element if it doesn't exist.
83909         if (!me.el) {
83910             me.surface.createSpriteElement(me);
83911         }
83912         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
83913         me.on('beforedestroy', me.dd.destroy, me.dd);
83914     },
83915
83916     /**
83917      * Change the attributes of the sprite.
83918      * @param {Object} attrs attributes to be changed on the sprite.
83919      * @param {Boolean} redraw Flag to immediatly draw the change.
83920      * @return {Ext.draw.Sprite} this
83921      */
83922     setAttributes: function(attrs, redraw) {
83923         var me = this,
83924             fontProps = me.fontProperties,
83925             fontPropsLength = fontProps.length,
83926             pathProps = me.pathProperties,
83927             pathPropsLength = pathProps.length,
83928             hasSurface = !!me.surface,
83929             custom = hasSurface && me.surface.customAttributes || {},
83930             spriteAttrs = me.attr,
83931             attr, i, translate, translation, rotate, rotation, scale, scaling;
83932
83933         attrs = Ext.apply({}, attrs);
83934         for (attr in custom) {
83935             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
83936                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
83937             }
83938         }
83939
83940         // Flag a change in hidden
83941         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
83942             me.dirtyHidden = true;
83943         }
83944
83945         // Flag path change
83946         for (i = 0; i < pathPropsLength; i++) {
83947             attr = pathProps[i];
83948             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83949                 me.dirtyPath = true;
83950                 break;
83951             }
83952         }
83953
83954         // Flag zIndex change
83955         if ('zIndex' in attrs) {
83956             me.zIndexDirty = true;
83957         }
83958
83959         // Flag font/text change
83960         for (i = 0; i < fontPropsLength; i++) {
83961             attr = fontProps[i];
83962             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83963                 me.dirtyFont = true;
83964                 break;
83965             }
83966         }
83967
83968         translate = attrs.translate;
83969         translation = spriteAttrs.translation;
83970         if (translate) {
83971             if ((translate.x && translate.x !== translation.x) ||
83972                 (translate.y && translate.y !== translation.y)) {
83973                 Ext.apply(translation, translate);
83974                 me.dirtyTransform = true;
83975             }
83976             delete attrs.translate;
83977         }
83978
83979         rotate = attrs.rotate;
83980         rotation = spriteAttrs.rotation;
83981         if (rotate) {
83982             if ((rotate.x && rotate.x !== rotation.x) ||
83983                 (rotate.y && rotate.y !== rotation.y) ||
83984                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
83985                 Ext.apply(rotation, rotate);
83986                 me.dirtyTransform = true;
83987             }
83988             delete attrs.rotate;
83989         }
83990
83991         scale = attrs.scale;
83992         scaling = spriteAttrs.scaling;
83993         if (scale) {
83994             if ((scale.x && scale.x !== scaling.x) ||
83995                 (scale.y && scale.y !== scaling.y) ||
83996                 (scale.cx && scale.cx !== scaling.cx) ||
83997                 (scale.cy && scale.cy !== scaling.cy)) {
83998                 Ext.apply(scaling, scale);
83999                 me.dirtyTransform = true;
84000             }
84001             delete attrs.scale;
84002         }
84003
84004         Ext.apply(spriteAttrs, attrs);
84005         me.dirty = true;
84006
84007         if (redraw === true && hasSurface) {
84008             me.redraw();
84009         }
84010         return this;
84011     },
84012
84013     /**
84014      * Retrieves the bounding box of the sprite.
84015      * This will be returned as an object with x, y, width, and height properties.
84016      * @return {Object} bbox
84017      */
84018     getBBox: function() {
84019         return this.surface.getBBox(this);
84020     },
84021
84022     setText: function(text) {
84023         return this.surface.setText(this, text);
84024     },
84025
84026     /**
84027      * Hides the sprite.
84028      * @param {Boolean} redraw Flag to immediatly draw the change.
84029      * @return {Ext.draw.Sprite} this
84030      */
84031     hide: function(redraw) {
84032         this.setAttributes({
84033             hidden: true
84034         }, redraw);
84035         return this;
84036     },
84037
84038     /**
84039      * Shows the sprite.
84040      * @param {Boolean} redraw Flag to immediatly draw the change.
84041      * @return {Ext.draw.Sprite} this
84042      */
84043     show: function(redraw) {
84044         this.setAttributes({
84045             hidden: false
84046         }, redraw);
84047         return this;
84048     },
84049
84050     /**
84051      * Removes the sprite.
84052      */
84053     remove: function() {
84054         if (this.surface) {
84055             this.surface.remove(this);
84056             return true;
84057         }
84058         return false;
84059     },
84060
84061     onRemove: function() {
84062         this.surface.onRemove(this);
84063     },
84064
84065     /**
84066      * Removes the sprite and clears all listeners.
84067      */
84068     destroy: function() {
84069         var me = this;
84070         if (me.fireEvent('beforedestroy', me) !== false) {
84071             me.remove();
84072             me.surface.onDestroy(me);
84073             me.clearListeners();
84074             me.fireEvent('destroy');
84075         }
84076     },
84077
84078     /**
84079      * Redraws the sprite.
84080      * @return {Ext.draw.Sprite} this
84081      */
84082     redraw: function() {
84083         this.surface.renderItem(this);
84084         return this;
84085     },
84086
84087     /**
84088      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
84089      * @param {String/Object} property The style property to be set, or an object of multiple styles.
84090      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
84091      * @return {Ext.draw.Sprite} this
84092      */
84093     setStyle: function() {
84094         this.el.setStyle.apply(this.el, arguments);
84095         return this;
84096     },
84097
84098     /**
84099      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
84100      * is severly limited in VML.
84101      * @param {String/String[]} className The CSS class to add, or an array of classes
84102      * @return {Ext.draw.Sprite} this
84103      */
84104     addCls: function(obj) {
84105         this.surface.addCls(this, obj);
84106         return this;
84107     },
84108
84109     /**
84110      * Removes one or more CSS classes from the element.
84111      * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
84112      * is severly limited in VML.
84113      * @return {Ext.draw.Sprite} this
84114      */
84115     removeCls: function(obj) {
84116         this.surface.removeCls(this, obj);
84117         return this;
84118     }
84119 });
84120
84121 /**
84122  * @class Ext.draw.engine.Svg
84123  * @extends Ext.draw.Surface
84124  * Provides specific methods to draw with SVG.
84125  */
84126 Ext.define('Ext.draw.engine.Svg', {
84127
84128     /* Begin Definitions */
84129
84130     extend: 'Ext.draw.Surface',
84131
84132     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84133
84134     /* End Definitions */
84135
84136     engine: 'Svg',
84137
84138     trimRe: /^\s+|\s+$/g,
84139     spacesRe: /\s+/,
84140     xlink: "http:/" + "/www.w3.org/1999/xlink",
84141
84142     translateAttrs: {
84143         radius: "r",
84144         radiusX: "rx",
84145         radiusY: "ry",
84146         path: "d",
84147         lineWidth: "stroke-width",
84148         fillOpacity: "fill-opacity",
84149         strokeOpacity: "stroke-opacity",
84150         strokeLinejoin: "stroke-linejoin"
84151     },
84152     
84153     parsers: {},
84154
84155     minDefaults: {
84156         circle: {
84157             cx: 0,
84158             cy: 0,
84159             r: 0,
84160             fill: "none",
84161             stroke: null,
84162             "stroke-width": null,
84163             opacity: null,
84164             "fill-opacity": null,
84165             "stroke-opacity": null
84166         },
84167         ellipse: {
84168             cx: 0,
84169             cy: 0,
84170             rx: 0,
84171             ry: 0,
84172             fill: "none",
84173             stroke: null,
84174             "stroke-width": null,
84175             opacity: null,
84176             "fill-opacity": null,
84177             "stroke-opacity": null
84178         },
84179         rect: {
84180             x: 0,
84181             y: 0,
84182             width: 0,
84183             height: 0,
84184             rx: 0,
84185             ry: 0,
84186             fill: "none",
84187             stroke: null,
84188             "stroke-width": null,
84189             opacity: null,
84190             "fill-opacity": null,
84191             "stroke-opacity": null
84192         },
84193         text: {
84194             x: 0,
84195             y: 0,
84196             "text-anchor": "start",
84197             "font-family": null,
84198             "font-size": null,
84199             "font-weight": null,
84200             "font-style": null,
84201             fill: "#000",
84202             stroke: null,
84203             "stroke-width": null,
84204             opacity: null,
84205             "fill-opacity": null,
84206             "stroke-opacity": null
84207         },
84208         path: {
84209             d: "M0,0",
84210             fill: "none",
84211             stroke: null,
84212             "stroke-width": null,
84213             opacity: null,
84214             "fill-opacity": null,
84215             "stroke-opacity": null
84216         },
84217         image: {
84218             x: 0,
84219             y: 0,
84220             width: 0,
84221             height: 0,
84222             preserveAspectRatio: "none",
84223             opacity: null
84224         }
84225     },
84226
84227     createSvgElement: function(type, attrs) {
84228         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
84229             key;
84230         if (attrs) {
84231             for (key in attrs) {
84232                 el.setAttribute(key, String(attrs[key]));
84233             }
84234         }
84235         return el;
84236     },
84237
84238     createSpriteElement: function(sprite) {
84239         // Create svg element and append to the DOM.
84240         var el = this.createSvgElement(sprite.type);
84241         el.id = sprite.id;
84242         if (el.style) {
84243             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
84244         }
84245         sprite.el = Ext.get(el);
84246         this.applyZIndex(sprite); //performs the insertion
84247         sprite.matrix = Ext.create('Ext.draw.Matrix');
84248         sprite.bbox = {
84249             plain: 0,
84250             transform: 0
84251         };
84252         sprite.fireEvent("render", sprite);
84253         return el;
84254     },
84255
84256     getBBox: function (sprite, isWithoutTransform) {
84257         var realPath = this["getPath" + sprite.type](sprite);
84258         if (isWithoutTransform) {
84259             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
84260             return sprite.bbox.plain;
84261         }
84262         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
84263         return sprite.bbox.transform;
84264     },
84265     
84266     getBBoxText: function (sprite) {
84267         var bbox = {},
84268             bb, height, width, i, ln, el;
84269
84270         if (sprite && sprite.el) {
84271             el = sprite.el.dom;
84272             try {
84273                 bbox = el.getBBox();
84274                 return bbox;
84275             } catch(e) {
84276                 // Firefox 3.0.x plays badly here
84277             }
84278             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
84279             ln = el.getNumberOfChars();
84280             for (i = 0; i < ln; i++) {
84281                 bb = el.getExtentOfChar(i);
84282                 bbox.y = Math.min(bb.y, bbox.y);
84283                 height = bb.y + bb.height - bbox.y;
84284                 bbox.height = Math.max(bbox.height, height);
84285                 width = bb.x + bb.width - bbox.x;
84286                 bbox.width = Math.max(bbox.width, width);
84287             }
84288             return bbox;
84289         }
84290     },
84291
84292     hide: function() {
84293         Ext.get(this.el).hide();
84294     },
84295
84296     show: function() {
84297         Ext.get(this.el).show();
84298     },
84299
84300     hidePrim: function(sprite) {
84301         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
84302     },
84303
84304     showPrim: function(sprite) {
84305         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
84306     },
84307
84308     getDefs: function() {
84309         return this._defs || (this._defs = this.createSvgElement("defs"));
84310     },
84311
84312     transform: function(sprite) {
84313         var me = this,
84314             matrix = Ext.create('Ext.draw.Matrix'),
84315             transforms = sprite.transformations,
84316             transformsLength = transforms.length,
84317             i = 0,
84318             transform, type;
84319             
84320         for (; i < transformsLength; i++) {
84321             transform = transforms[i];
84322             type = transform.type;
84323             if (type == "translate") {
84324                 matrix.translate(transform.x, transform.y);
84325             }
84326             else if (type == "rotate") {
84327                 matrix.rotate(transform.degrees, transform.x, transform.y);
84328             }
84329             else if (type == "scale") {
84330                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
84331             }
84332         }
84333         sprite.matrix = matrix;
84334         sprite.el.set({transform: matrix.toSvg()});
84335     },
84336
84337     setSize: function(w, h) {
84338         var me = this,
84339             el = me.el;
84340         
84341         w = +w || me.width;
84342         h = +h || me.height;
84343         me.width = w;
84344         me.height = h;
84345
84346         el.setSize(w, h);
84347         el.set({
84348             width: w,
84349             height: h
84350         });
84351         me.callParent([w, h]);
84352     },
84353
84354     /**
84355      * Get the region for the surface's canvas area
84356      * @returns {Ext.util.Region}
84357      */
84358     getRegion: function() {
84359         // Mozilla requires using the background rect because the svg element returns an
84360         // incorrect region. Webkit gives no region for the rect and must use the svg element.
84361         var svgXY = this.el.getXY(),
84362             rectXY = this.bgRect.getXY(),
84363             max = Math.max,
84364             x = max(svgXY[0], rectXY[0]),
84365             y = max(svgXY[1], rectXY[1]);
84366         return {
84367             left: x,
84368             top: y,
84369             right: x + this.width,
84370             bottom: y + this.height
84371         };
84372     },
84373
84374     onRemove: function(sprite) {
84375         if (sprite.el) {
84376             sprite.el.remove();
84377             delete sprite.el;
84378         }
84379         this.callParent(arguments);
84380     },
84381     
84382     setViewBox: function(x, y, width, height) {
84383         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
84384             this.callParent(arguments);
84385             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
84386         }
84387     },
84388
84389     render: function (container) {
84390         var me = this;
84391         if (!me.el) {
84392             var width = me.width || 10,
84393                 height = me.height || 10,
84394                 el = me.createSvgElement('svg', {
84395                     xmlns: "http:/" + "/www.w3.org/2000/svg",
84396                     version: 1.1,
84397                     width: width,
84398                     height: height
84399                 }),
84400                 defs = me.getDefs(),
84401
84402                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
84403                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
84404                 // use it rather than the svg element for retrieving the correct client rect of the
84405                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
84406                 bgRect = me.createSvgElement("rect", {
84407                     width: "100%",
84408                     height: "100%",
84409                     fill: "#000",
84410                     stroke: "none",
84411                     opacity: 0
84412                 }),
84413                 webkitRect;
84414             
84415                 if (Ext.isSafari3) {
84416                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
84417                     webkitRect = me.createSvgElement("rect", {
84418                         x: -10,
84419                         y: -10,
84420                         width: "110%",
84421                         height: "110%",
84422                         fill: "none",
84423                         stroke: "#000"
84424                     });
84425                 }
84426             el.appendChild(defs);
84427             if (Ext.isSafari3) {
84428                 el.appendChild(webkitRect);
84429             }
84430             el.appendChild(bgRect);
84431             container.appendChild(el);
84432             me.el = Ext.get(el);
84433             me.bgRect = Ext.get(bgRect);
84434             if (Ext.isSafari3) {
84435                 me.webkitRect = Ext.get(webkitRect);
84436                 me.webkitRect.hide();
84437             }
84438             me.el.on({
84439                 scope: me,
84440                 mouseup: me.onMouseUp,
84441                 mousedown: me.onMouseDown,
84442                 mouseover: me.onMouseOver,
84443                 mouseout: me.onMouseOut,
84444                 mousemove: me.onMouseMove,
84445                 mouseenter: me.onMouseEnter,
84446                 mouseleave: me.onMouseLeave,
84447                 click: me.onClick
84448             });
84449         }
84450         me.renderAll();
84451     },
84452
84453     // private
84454     onMouseEnter: function(e) {
84455         if (this.el.parent().getRegion().contains(e.getPoint())) {
84456             this.fireEvent('mouseenter', e);
84457         }
84458     },
84459
84460     // private
84461     onMouseLeave: function(e) {
84462         if (!this.el.parent().getRegion().contains(e.getPoint())) {
84463             this.fireEvent('mouseleave', e);
84464         }
84465     },
84466     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
84467     processEvent: function(name, e) {
84468         var target = e.getTarget(),
84469             surface = this.surface,
84470             sprite;
84471
84472         this.fireEvent(name, e);
84473         // We wrap text types in a tspan, sprite is the parent.
84474         if (target.nodeName == "tspan" && target.parentNode) {
84475             target = target.parentNode;
84476         }
84477         sprite = this.items.get(target.id);
84478         if (sprite) {
84479             sprite.fireEvent(name, sprite, e);
84480         }
84481     },
84482
84483     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
84484      * the baseline for text the vertical middle of the text to be the same as VML.
84485      */
84486     tuneText: function (sprite, attrs) {
84487         var el = sprite.el.dom,
84488             tspans = [],
84489             height, tspan, text, i, ln, texts, factor;
84490
84491         if (attrs.hasOwnProperty("text")) {
84492            tspans = this.setText(sprite, attrs.text);
84493         }
84494         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
84495         if (tspans.length) {
84496             height = this.getBBoxText(sprite).height;
84497             for (i = 0, ln = tspans.length; i < ln; i++) {
84498                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
84499                 // so we are going to normalize that here
84500                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
84501                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
84502             }
84503             sprite.dirty = true;
84504         }
84505     },
84506
84507     setText: function(sprite, textString) {
84508          var me = this,
84509              el = sprite.el.dom,
84510              x = el.getAttribute("x"),
84511              tspans = [],
84512              height, tspan, text, i, ln, texts;
84513         
84514         while (el.firstChild) {
84515             el.removeChild(el.firstChild);
84516         }
84517         // Wrap each row into tspan to emulate rows
84518         texts = String(textString).split("\n");
84519         for (i = 0, ln = texts.length; i < ln; i++) {
84520             text = texts[i];
84521             if (text) {
84522                 tspan = me.createSvgElement("tspan");
84523                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
84524                 tspan.setAttribute("x", x);
84525                 el.appendChild(tspan);
84526                 tspans[i] = tspan;
84527             }
84528         }
84529         return tspans;
84530     },
84531
84532     renderAll: function() {
84533         this.items.each(this.renderItem, this);
84534     },
84535
84536     renderItem: function (sprite) {
84537         if (!this.el) {
84538             return;
84539         }
84540         if (!sprite.el) {
84541             this.createSpriteElement(sprite);
84542         }
84543         if (sprite.zIndexDirty) {
84544             this.applyZIndex(sprite);
84545         }
84546         if (sprite.dirty) {
84547             this.applyAttrs(sprite);
84548             this.applyTransformations(sprite);
84549         }
84550     },
84551
84552     redraw: function(sprite) {
84553         sprite.dirty = sprite.zIndexDirty = true;
84554         this.renderItem(sprite);
84555     },
84556
84557     applyAttrs: function (sprite) {
84558         var me = this,
84559             el = sprite.el,
84560             group = sprite.group,
84561             sattr = sprite.attr,
84562             parsers = me.parsers,
84563             //Safari does not handle linear gradients correctly in quirksmode
84564             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84565             //ref: EXTJSIV-1472
84566             gradientsMap = me.gradientsMap || {},
84567             safariFix = Ext.isSafari && !Ext.isStrict,
84568             groups, i, ln, attrs, font, key, style, name, rect;
84569
84570         if (group) {
84571             groups = [].concat(group);
84572             ln = groups.length;
84573             for (i = 0; i < ln; i++) {
84574                 group = groups[i];
84575                 me.getGroup(group).add(sprite);
84576             }
84577             delete sprite.group;
84578         }
84579         attrs = me.scrubAttrs(sprite) || {};
84580
84581         // if (sprite.dirtyPath) {
84582             sprite.bbox.plain = 0;
84583             sprite.bbox.transform = 0;
84584             if (sprite.type == "circle" || sprite.type == "ellipse") {
84585                 attrs.cx = attrs.cx || attrs.x;
84586                 attrs.cy = attrs.cy || attrs.y;
84587             }
84588             else if (sprite.type == "rect") {
84589                 attrs.rx = attrs.ry = attrs.r;
84590             }
84591             else if (sprite.type == "path" && attrs.d) {
84592                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
84593             }
84594             sprite.dirtyPath = false;
84595         // }
84596         // else {
84597         //     delete attrs.d;
84598         // }
84599
84600         if (attrs['clip-rect']) {
84601             me.setClip(sprite, attrs);
84602             delete attrs['clip-rect'];
84603         }
84604         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
84605             el.set({ style: "font: " + attrs.font});
84606             sprite.dirtyFont = false;
84607         }
84608         if (sprite.type == "image") {
84609             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
84610         }
84611         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
84612
84613         if (sprite.dirtyHidden) {
84614             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
84615             sprite.dirtyHidden = false;
84616         }
84617         for (key in attrs) {
84618             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
84619                 //Safari does not handle linear gradients correctly in quirksmode
84620                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84621                 //ref: EXTJSIV-1472
84622                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
84623                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
84624                 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
84625                     attrs[key] = gradientsMap[attrs[key]];
84626                 }
84627                 if (key in parsers) {
84628                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
84629                 } else {
84630                     el.dom.setAttribute(key, attrs[key]);
84631                 }
84632             }
84633         }
84634         
84635         if (sprite.type == 'text') {
84636             me.tuneText(sprite, attrs);
84637         }
84638
84639         //set styles
84640         style = sattr.style;
84641         if (style) {
84642             el.setStyle(style);
84643         }
84644
84645         sprite.dirty = false;
84646
84647         if (Ext.isSafari3) {
84648             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
84649             me.webkitRect.show();
84650             setTimeout(function () {
84651                 me.webkitRect.hide();
84652             });
84653         }
84654     },
84655
84656     setClip: function(sprite, params) {
84657         var me = this,
84658             rect = params["clip-rect"],
84659             clipEl, clipPath;
84660         if (rect) {
84661             if (sprite.clip) {
84662                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
84663             }
84664             clipEl = me.createSvgElement('clipPath');
84665             clipPath = me.createSvgElement('rect');
84666             clipEl.id = Ext.id(null, 'ext-clip-');
84667             clipPath.setAttribute("x", rect.x);
84668             clipPath.setAttribute("y", rect.y);
84669             clipPath.setAttribute("width", rect.width);
84670             clipPath.setAttribute("height", rect.height);
84671             clipEl.appendChild(clipPath);
84672             me.getDefs().appendChild(clipEl);
84673             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
84674             sprite.clip = clipPath;
84675         }
84676         // if (!attrs[key]) {
84677         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
84678         //     clip && clip.parentNode.removeChild(clip);
84679         //     sprite.el.setAttribute("clip-path", "");
84680         //     delete attrss.clip;
84681         // }
84682     },
84683
84684     /**
84685      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
84686      * @param {Ext.draw.Sprite} sprite
84687      */
84688     applyZIndex: function(sprite) {
84689         var me = this,
84690             items = me.items,
84691             idx = items.indexOf(sprite),
84692             el = sprite.el,
84693             prevEl;
84694         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
84695             if (idx > 0) {
84696                 // Find the first previous sprite which has its DOM element created already
84697                 do {
84698                     prevEl = items.getAt(--idx).el;
84699                 } while (!prevEl && idx > 0);
84700             }
84701             el.insertAfter(prevEl || me.bgRect);
84702         }
84703         sprite.zIndexDirty = false;
84704     },
84705
84706     createItem: function (config) {
84707         var sprite = Ext.create('Ext.draw.Sprite', config);
84708         sprite.surface = this;
84709         return sprite;
84710     },
84711
84712     addGradient: function(gradient) {
84713         gradient = Ext.draw.Draw.parseGradient(gradient);
84714         var me = this,
84715             ln = gradient.stops.length,
84716             vector = gradient.vector,
84717             //Safari does not handle linear gradients correctly in quirksmode
84718             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84719             //ref: EXTJSIV-1472
84720             usePlain = Ext.isSafari && !Ext.isStrict,
84721             gradientEl, stop, stopEl, i, gradientsMap;
84722             
84723         gradientsMap = me.gradientsMap || {};
84724         
84725         if (!usePlain) {
84726             if (gradient.type == "linear") {
84727                 gradientEl = me.createSvgElement("linearGradient");
84728                 gradientEl.setAttribute("x1", vector[0]);
84729                 gradientEl.setAttribute("y1", vector[1]);
84730                 gradientEl.setAttribute("x2", vector[2]);
84731                 gradientEl.setAttribute("y2", vector[3]);
84732             }
84733             else {
84734                 gradientEl = me.createSvgElement("radialGradient");
84735                 gradientEl.setAttribute("cx", gradient.centerX);
84736                 gradientEl.setAttribute("cy", gradient.centerY);
84737                 gradientEl.setAttribute("r", gradient.radius);
84738                 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
84739                     gradientEl.setAttribute("fx", gradient.focalX);
84740                     gradientEl.setAttribute("fy", gradient.focalY);
84741                 }
84742             }
84743             gradientEl.id = gradient.id;
84744             me.getDefs().appendChild(gradientEl);
84745             for (i = 0; i < ln; i++) {
84746                 stop = gradient.stops[i];
84747                 stopEl = me.createSvgElement("stop");
84748                 stopEl.setAttribute("offset", stop.offset + "%");
84749                 stopEl.setAttribute("stop-color", stop.color);
84750                 stopEl.setAttribute("stop-opacity",stop.opacity);
84751                 gradientEl.appendChild(stopEl);
84752             }
84753         } else {
84754             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
84755         }
84756         me.gradientsMap = gradientsMap;
84757     },
84758
84759     /**
84760      * Checks if the specified CSS class exists on this element's DOM node.
84761      * @param {String} className The CSS class to check for
84762      * @return {Boolean} True if the class exists, else false
84763      */
84764     hasCls: function(sprite, className) {
84765         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
84766     },
84767
84768     addCls: function(sprite, className) {
84769         var el = sprite.el,
84770             i,
84771             len,
84772             v,
84773             cls = [],
84774             curCls =  el.getAttribute('class') || '';
84775         // Separate case is for speed
84776         if (!Ext.isArray(className)) {
84777             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
84778                 el.set({ 'class': curCls + ' ' + className });
84779             }
84780         }
84781         else {
84782             for (i = 0, len = className.length; i < len; i++) {
84783                 v = className[i];
84784                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
84785                     cls.push(v);
84786                 }
84787             }
84788             if (cls.length) {
84789                 el.set({ 'class': ' ' + cls.join(' ') });
84790             }
84791         }
84792     },
84793
84794     removeCls: function(sprite, className) {
84795         var me = this,
84796             el = sprite.el,
84797             curCls =  el.getAttribute('class') || '',
84798             i, idx, len, cls, elClasses;
84799         if (!Ext.isArray(className)){
84800             className = [className];
84801         }
84802         if (curCls) {
84803             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
84804             for (i = 0, len = className.length; i < len; i++) {
84805                 cls = className[i];
84806                 if (typeof cls == 'string') {
84807                     cls = cls.replace(me.trimRe, '');
84808                     idx = Ext.Array.indexOf(elClasses, cls);
84809                     if (idx != -1) {
84810                         Ext.Array.erase(elClasses, idx, 1);
84811                     }
84812                 }
84813             }
84814             el.set({ 'class': elClasses.join(' ') });
84815         }
84816     },
84817
84818     destroy: function() {
84819         var me = this;
84820         
84821         me.callParent();
84822         if (me.el) {
84823             me.el.remove();
84824         }
84825         delete me.el;
84826     }
84827 });
84828 /**
84829  * @class Ext.draw.engine.Vml
84830  * @extends Ext.draw.Surface
84831  * Provides specific methods to draw with VML.
84832  */
84833
84834 Ext.define('Ext.draw.engine.Vml', {
84835
84836     /* Begin Definitions */
84837
84838     extend: 'Ext.draw.Surface',
84839
84840     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84841
84842     /* End Definitions */
84843
84844     engine: 'Vml',
84845
84846     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
84847     bitesRe: /([clmz]),?([^clmz]*)/gi,
84848     valRe: /-?[^,\s-]+/g,
84849     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
84850     pathlike: /^(path|rect)$/,
84851     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
84852     partialPathRe: /[clmz]/g,
84853     fontFamilyRe: /^['"]+|['"]+$/g,
84854     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
84855     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
84856     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
84857     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
84858     zoom: 21600,
84859     coordsize: 1000,
84860     coordorigin: '0 0',
84861
84862     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
84863     orderSpritesByZIndex: false,
84864
84865     // @private
84866     // Convert an SVG standard path into a VML path
84867     path2vml: function (path) {
84868         var me = this,
84869             nonVML =  me.NonVmlPathRe,
84870             map = me.map,
84871             val = me.valRe,
84872             zoom = me.zoom,
84873             bites = me.bitesRe,
84874             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
84875             res, pa, p, r, i, ii, j, jj;
84876         if (String(path).match(nonVML)) {
84877             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
84878         } else if (!String(path).match(me.partialPathRe)) {
84879             res = String(path).replace(bites, function (all, command, args) {
84880                 var vals = [],
84881                     isMove = command.toLowerCase() == "m",
84882                     res = map[command];
84883                 args.replace(val, function (value) {
84884                     if (isMove && vals[length] == 2) {
84885                         res += vals + map[command == "m" ? "l" : "L"];
84886                         vals = [];
84887                     }
84888                     vals.push(Math.round(value * zoom));
84889                 });
84890                 return res + vals;
84891             });
84892             return res;
84893         }
84894         pa = command(path);
84895         res = [];
84896         for (i = 0, ii = pa.length; i < ii; i++) {
84897             p = pa[i];
84898             r = pa[i][0].toLowerCase();
84899             if (r == "z") {
84900                 r = "x";
84901             }
84902             for (j = 1, jj = p.length; j < jj; j++) {
84903                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
84904             }
84905             res.push(r);
84906         }
84907         return res.join(" ");
84908     },
84909
84910     // @private - set of attributes which need to be translated from the sprite API to the native browser API
84911     translateAttrs: {
84912         radius: "r",
84913         radiusX: "rx",
84914         radiusY: "ry",
84915         lineWidth: "stroke-width",
84916         fillOpacity: "fill-opacity",
84917         strokeOpacity: "stroke-opacity",
84918         strokeLinejoin: "stroke-linejoin"
84919     },
84920
84921     // @private - Minimun set of defaults for different types of sprites.
84922     minDefaults: {
84923         circle: {
84924             fill: "none",
84925             stroke: null,
84926             "stroke-width": null,
84927             opacity: null,
84928             "fill-opacity": null,
84929             "stroke-opacity": null
84930         },
84931         ellipse: {
84932             cx: 0,
84933             cy: 0,
84934             rx: 0,
84935             ry: 0,
84936             fill: "none",
84937             stroke: null,
84938             "stroke-width": null,
84939             opacity: null,
84940             "fill-opacity": null,
84941             "stroke-opacity": null
84942         },
84943         rect: {
84944             x: 0,
84945             y: 0,
84946             width: 0,
84947             height: 0,
84948             rx: 0,
84949             ry: 0,
84950             fill: "none",
84951             stroke: null,
84952             "stroke-width": null,
84953             opacity: null,
84954             "fill-opacity": null,
84955             "stroke-opacity": null
84956         },
84957         text: {
84958             x: 0,
84959             y: 0,
84960             "text-anchor": "start",
84961             font: '10px "Arial"',
84962             fill: "#000",
84963             stroke: null,
84964             "stroke-width": null,
84965             opacity: null,
84966             "fill-opacity": null,
84967             "stroke-opacity": null
84968         },
84969         path: {
84970             d: "M0,0",
84971             fill: "none",
84972             stroke: null,
84973             "stroke-width": null,
84974             opacity: null,
84975             "fill-opacity": null,
84976             "stroke-opacity": null
84977         },
84978         image: {
84979             x: 0,
84980             y: 0,
84981             width: 0,
84982             height: 0,
84983             preserveAspectRatio: "none",
84984             opacity: null
84985         }
84986     },
84987
84988     // private
84989     onMouseEnter: function(e) {
84990         this.fireEvent("mouseenter", e);
84991     },
84992
84993     // private
84994     onMouseLeave: function(e) {
84995         this.fireEvent("mouseleave", e);
84996     },
84997
84998     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
84999     processEvent: function(name, e) {
85000         var target = e.getTarget(),
85001             surface = this.surface,
85002             sprite;
85003         this.fireEvent(name, e);
85004         sprite = this.items.get(target.id);
85005         if (sprite) {
85006             sprite.fireEvent(name, sprite, e);
85007         }
85008     },
85009
85010     // Create the VML element/elements and append them to the DOM
85011     createSpriteElement: function(sprite) {
85012         var me = this,
85013             attr = sprite.attr,
85014             type = sprite.type,
85015             zoom = me.zoom,
85016             vml = sprite.vml || (sprite.vml = {}),
85017             round = Math.round,
85018             el = me.createNode('shape'),
85019             path, skew, textPath;
85020
85021         el.coordsize = zoom + ' ' + zoom;
85022         el.coordorigin = attr.coordorigin || "0 0";
85023         Ext.get(el).addCls(me.spriteCls);
85024         if (type == "text") {
85025             vml.path = path = me.createNode("path");
85026             path.textpathok = true;
85027             vml.textpath = textPath = me.createNode("textpath");
85028             textPath.on = true;
85029             el.appendChild(textPath);
85030             el.appendChild(path);
85031         }
85032         el.id = sprite.id;
85033         sprite.el = Ext.get(el);
85034         me.el.appendChild(el);
85035         if (type !== 'image') {
85036             skew = me.createNode("skew");
85037             skew.on = true;
85038             el.appendChild(skew);
85039             sprite.skew = skew;
85040         }
85041         sprite.matrix = Ext.create('Ext.draw.Matrix');
85042         sprite.bbox = {
85043             plain: null,
85044             transform: null
85045         };
85046         sprite.fireEvent("render", sprite);
85047         return sprite.el;
85048     },
85049
85050     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
85051     getBBox: function (sprite, isWithoutTransform) {
85052         var realPath = this["getPath" + sprite.type](sprite);
85053         if (isWithoutTransform) {
85054             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
85055             return sprite.bbox.plain;
85056         }
85057         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
85058         return sprite.bbox.transform;
85059     },
85060
85061     getBBoxText: function (sprite) {
85062         var vml = sprite.vml;
85063         return {
85064             x: vml.X + (vml.bbx || 0) - vml.W / 2,
85065             y: vml.Y - vml.H / 2,
85066             width: vml.W,
85067             height: vml.H
85068         };
85069     },
85070
85071     applyAttrs: function (sprite) {
85072         var me = this,
85073             vml = sprite.vml,
85074             group = sprite.group,
85075             spriteAttr = sprite.attr,
85076             el = sprite.el,
85077             dom = el.dom,
85078             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
85079
85080         if (group) {
85081             groups = [].concat(group);
85082             ln = groups.length;
85083             for (i = 0; i < ln; i++) {
85084                 group = groups[i];
85085                 me.getGroup(group).add(sprite);
85086             }
85087             delete sprite.group;
85088         }
85089         scrubbedAttrs = me.scrubAttrs(sprite) || {};
85090
85091         if (sprite.zIndexDirty) {
85092             me.setZIndex(sprite);
85093         }
85094
85095         // Apply minimum default attributes
85096         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
85097
85098         if (dom.href) {
85099             dom.href = scrubbedAttrs.href;
85100         }
85101         if (dom.title) {
85102             dom.title = scrubbedAttrs.title;
85103         }
85104         if (dom.target) {
85105             dom.target = scrubbedAttrs.target;
85106         }
85107         if (dom.cursor) {
85108             dom.cursor = scrubbedAttrs.cursor;
85109         }
85110
85111         // Change visibility
85112         if (sprite.dirtyHidden) {
85113             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
85114             sprite.dirtyHidden = false;
85115         }
85116
85117         // Update path
85118         if (sprite.dirtyPath) {
85119             if (sprite.type == "circle" || sprite.type == "ellipse") {
85120                 var cx = scrubbedAttrs.x,
85121                     cy = scrubbedAttrs.y,
85122                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
85123                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
85124                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
85125                             Math.round((cx - rx) * me.zoom),
85126                             Math.round((cy - ry) * me.zoom),
85127                             Math.round((cx + rx) * me.zoom),
85128                             Math.round((cy + ry) * me.zoom),
85129                             Math.round(cx * me.zoom));
85130                 sprite.dirtyPath = false;
85131             }
85132             else if (sprite.type !== "text") {
85133                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
85134                 dom.path = me.path2vml(scrubbedAttrs.path);
85135                 sprite.dirtyPath = false;
85136             }
85137         }
85138
85139         // Apply clipping
85140         if ("clip-rect" in scrubbedAttrs) {
85141             me.setClip(sprite, scrubbedAttrs);
85142         }
85143
85144         // Handle text (special handling required)
85145         if (sprite.type == "text") {
85146             me.setTextAttributes(sprite, scrubbedAttrs);
85147         }
85148
85149         // Handle fill and opacity
85150         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
85151             me.setFill(sprite, scrubbedAttrs);
85152         }
85153
85154         // Handle stroke (all fills require a stroke element)
85155         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
85156             me.setStroke(sprite, scrubbedAttrs);
85157         }
85158         
85159         //set styles
85160         style = spriteAttr.style;
85161         if (style) {
85162             el.setStyle(style);
85163         }
85164
85165         sprite.dirty = false;
85166     },
85167
85168     setZIndex: function(sprite) {
85169         if (sprite.el) {
85170             if (sprite.attr.zIndex != undefined) {
85171                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
85172             }
85173             sprite.zIndexDirty = false;
85174         }
85175     },
85176
85177     // Normalize all virtualized types into paths.
85178     setPaths: function(sprite, params) {
85179         var spriteAttr = sprite.attr;
85180         // Clear bbox cache
85181         sprite.bbox.plain = null;
85182         sprite.bbox.transform = null;
85183         if (sprite.type == 'circle') {
85184             spriteAttr.rx = spriteAttr.ry = params.r;
85185             return Ext.draw.Draw.ellipsePath(sprite);
85186         }
85187         else if (sprite.type == 'ellipse') {
85188             spriteAttr.rx = params.rx;
85189             spriteAttr.ry = params.ry;
85190             return Ext.draw.Draw.ellipsePath(sprite);
85191         }
85192         else if (sprite.type == 'rect' || sprite.type == 'image') {
85193             spriteAttr.rx = spriteAttr.ry = params.r;
85194             return Ext.draw.Draw.rectPath(sprite);
85195         }
85196         else if (sprite.type == 'path' && spriteAttr.path) {
85197             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
85198         }
85199         return false;
85200     },
85201
85202     setFill: function(sprite, params) {
85203         var me = this,
85204             el = sprite.el,
85205             dom = el.dom,
85206             fillEl = dom.getElementsByTagName('fill')[0],
85207             opacity, gradient, fillUrl, rotation, angle;
85208
85209         if (fillEl) {
85210             dom.removeChild(fillEl);
85211         } else {
85212             fillEl = me.createNode('fill');
85213         }
85214         if (Ext.isArray(params.fill)) {
85215             params.fill = params.fill[0];
85216         }
85217         if (sprite.type == 'image') {
85218             fillEl.on = true;
85219             fillEl.src = params.src;
85220             fillEl.type = "tile";
85221             fillEl.rotate = true;
85222         } else if (params.fill == "none") {
85223             fillEl.on = false;
85224         } else {
85225             if (typeof params.opacity == "number") {
85226                 fillEl.opacity = params.opacity;
85227             }
85228             if (typeof params["fill-opacity"] == "number") {
85229                 fillEl.opacity = params["fill-opacity"];
85230             }
85231             fillEl.on = true;
85232             if (typeof params.fill == "string") {
85233                 fillUrl = params.fill.match(me.fillUrlRe);
85234                 if (fillUrl) {
85235                     fillUrl = fillUrl[1];
85236                     // If the URL matches one of the registered gradients, render that gradient
85237                     if (fillUrl.charAt(0) == "#") {
85238                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
85239                     }
85240                     if (gradient) {
85241                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
85242                         rotation = params.rotation;
85243                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
85244                         // IE will flip the angle at 0 degrees...
85245                         if (angle === 0) {
85246                             angle = 180;
85247                         }
85248                         fillEl.angle = angle;
85249                         fillEl.type = "gradient";
85250                         fillEl.method = "sigma";
85251                         fillEl.colors = gradient.colors;
85252                     }
85253                     // Otherwise treat it as an image
85254                     else {
85255                         fillEl.src = fillUrl;
85256                         fillEl.type = "tile";
85257                         fillEl.rotate = true;
85258                     }
85259                 }
85260                 else {
85261                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
85262                     fillEl.src = "";
85263                     fillEl.type = "solid";
85264                 }
85265             }
85266         }
85267         dom.appendChild(fillEl);
85268     },
85269
85270     setStroke: function(sprite, params) {
85271         var me = this,
85272             el = sprite.el.dom,
85273             strokeEl = sprite.strokeEl,
85274             newStroke = false,
85275             width, opacity;
85276
85277         if (!strokeEl) {
85278             strokeEl = sprite.strokeEl = me.createNode("stroke");
85279             newStroke = true;
85280         }
85281         if (Ext.isArray(params.stroke)) {
85282             params.stroke = params.stroke[0];
85283         }
85284         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
85285             strokeEl.on = false;
85286         }
85287         else {
85288             strokeEl.on = true;
85289             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
85290                 // VML does NOT support a gradient stroke :(
85291                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
85292             }
85293             strokeEl.joinstyle = params["stroke-linejoin"];
85294             strokeEl.endcap = params["stroke-linecap"] || "round";
85295             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
85296             width = parseFloat(params["stroke-width"] || 1) * 0.75;
85297             opacity = params["stroke-opacity"] || 1;
85298             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
85299             if (Ext.isNumber(width) && width < 1) {
85300                 strokeEl.weight = 1;
85301                 strokeEl.opacity = opacity * width;
85302             }
85303             else {
85304                 strokeEl.weight = width;
85305                 strokeEl.opacity = opacity;
85306             }
85307         }
85308         if (newStroke) {
85309             el.appendChild(strokeEl);
85310         }
85311     },
85312
85313     setClip: function(sprite, params) {
85314         var me = this,
85315             el = sprite.el,
85316             clipEl = sprite.clipEl,
85317             rect = String(params["clip-rect"]).split(me.separatorRe);
85318         if (!clipEl) {
85319             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
85320             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
85321         }
85322         if (rect.length == 4) {
85323             rect[2] = +rect[2] + (+rect[0]);
85324             rect[3] = +rect[3] + (+rect[1]);
85325             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
85326             clipEl.setSize(me.el.width, me.el.height);
85327         }
85328         else {
85329             clipEl.setStyle("clip", "");
85330         }
85331     },
85332
85333     setTextAttributes: function(sprite, params) {
85334         var me = this,
85335             vml = sprite.vml,
85336             textStyle = vml.textpath.style,
85337             spanCacheStyle = me.span.style,
85338             zoom = me.zoom,
85339             round = Math.round,
85340             fontObj = {
85341                 fontSize: "font-size",
85342                 fontWeight: "font-weight",
85343                 fontStyle: "font-style"
85344             },
85345             fontProp,
85346             paramProp;
85347         if (sprite.dirtyFont) {
85348             if (params.font) {
85349                 textStyle.font = spanCacheStyle.font = params.font;
85350             }
85351             if (params["font-family"]) {
85352                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
85353                 spanCacheStyle.fontFamily = params["font-family"];
85354             }
85355
85356             for (fontProp in fontObj) {
85357                 paramProp = params[fontObj[fontProp]];
85358                 if (paramProp) {
85359                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
85360                 }
85361             }
85362
85363             me.setText(sprite, params.text);
85364             
85365             if (vml.textpath.string) {
85366                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
85367             }
85368             vml.W = me.span.offsetWidth;
85369             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
85370
85371             // text-anchor emulation
85372             if (params["text-anchor"] == "middle") {
85373                 textStyle["v-text-align"] = "center";
85374             }
85375             else if (params["text-anchor"] == "end") {
85376                 textStyle["v-text-align"] = "right";
85377                 vml.bbx = -Math.round(vml.W / 2);
85378             }
85379             else {
85380                 textStyle["v-text-align"] = "left";
85381                 vml.bbx = Math.round(vml.W / 2);
85382             }
85383         }
85384         vml.X = params.x;
85385         vml.Y = params.y;
85386         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);
85387         // Clear bbox cache
85388         sprite.bbox.plain = null;
85389         sprite.bbox.transform = null;
85390         sprite.dirtyFont = false;
85391     },
85392     
85393     setText: function(sprite, text) {
85394         sprite.vml.textpath.string = Ext.htmlDecode(text);
85395     },
85396
85397     hide: function() {
85398         this.el.hide();
85399     },
85400
85401     show: function() {
85402         this.el.show();
85403     },
85404
85405     hidePrim: function(sprite) {
85406         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
85407     },
85408
85409     showPrim: function(sprite) {
85410         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
85411     },
85412
85413     setSize: function(width, height) {
85414         var me = this;
85415         width = width || me.width;
85416         height = height || me.height;
85417         me.width = width;
85418         me.height = height;
85419
85420         if (me.el) {
85421             // Size outer div
85422             if (width != undefined) {
85423                 me.el.setWidth(width);
85424             }
85425             if (height != undefined) {
85426                 me.el.setHeight(height);
85427             }
85428
85429             // Handle viewBox sizing
85430             me.applyViewBox();
85431
85432             me.callParent(arguments);
85433         }
85434     },
85435
85436     setViewBox: function(x, y, width, height) {
85437         this.callParent(arguments);
85438         this.viewBox = {
85439             x: x,
85440             y: y,
85441             width: width,
85442             height: height
85443         };
85444         this.applyViewBox();
85445     },
85446
85447     /**
85448      * @private Using the current viewBox property and the surface's width and height, calculate the
85449      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
85450      */
85451     applyViewBox: function() {
85452         var me = this,
85453             viewBox = me.viewBox,
85454             width = me.width,
85455             height = me.height,
85456             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
85457             relativeHeight, relativeWidth, size;
85458
85459         if (viewBox && (width || height)) {
85460             viewBoxX = viewBox.x;
85461             viewBoxY = viewBox.y;
85462             viewBoxWidth = viewBox.width;
85463             viewBoxHeight = viewBox.height;
85464             relativeHeight = height / viewBoxHeight;
85465             relativeWidth = width / viewBoxWidth;
85466
85467             if (viewBoxWidth * relativeHeight < width) {
85468                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
85469             }
85470             if (viewBoxHeight * relativeWidth < height) {
85471                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
85472             }
85473
85474             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
85475
85476             me.viewBoxShift = {
85477                 dx: -viewBoxX,
85478                 dy: -viewBoxY,
85479                 scale: size
85480             };
85481             me.items.each(function(item) {
85482                 me.transform(item);
85483             });
85484         }
85485     },
85486
85487     onAdd: function(item) {
85488         this.callParent(arguments);
85489         if (this.el) {
85490             this.renderItem(item);
85491         }
85492     },
85493
85494     onRemove: function(sprite) {
85495         if (sprite.el) {
85496             sprite.el.remove();
85497             delete sprite.el;
85498         }
85499         this.callParent(arguments);
85500     },
85501
85502     // VML Node factory method (createNode)
85503     createNode : (function () {
85504         try {
85505             var doc = Ext.getDoc().dom;
85506             if (!doc.namespaces.rvml) {
85507                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
85508             }
85509             return function (tagName) {
85510                 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
85511             };
85512         } catch (e) {
85513             return function (tagName) {
85514                 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
85515             };
85516         }
85517     })(),
85518
85519     render: function (container) {
85520         var me = this,
85521             doc = Ext.getDoc().dom;
85522
85523         if (!me.el) {
85524             var el = doc.createElement("div");
85525             me.el = Ext.get(el);
85526             me.el.addCls(me.baseVmlCls);
85527
85528             // Measuring span (offscrren)
85529             me.span = doc.createElement("span");
85530             Ext.get(me.span).addCls(me.measureSpanCls);
85531             el.appendChild(me.span);
85532             me.el.setSize(me.width || 10, me.height || 10);
85533             container.appendChild(el);
85534             me.el.on({
85535                 scope: me,
85536                 mouseup: me.onMouseUp,
85537                 mousedown: me.onMouseDown,
85538                 mouseover: me.onMouseOver,
85539                 mouseout: me.onMouseOut,
85540                 mousemove: me.onMouseMove,
85541                 mouseenter: me.onMouseEnter,
85542                 mouseleave: me.onMouseLeave,
85543                 click: me.onClick
85544             });
85545         }
85546         me.renderAll();
85547     },
85548
85549     renderAll: function() {
85550         this.items.each(this.renderItem, this);
85551     },
85552
85553     redraw: function(sprite) {
85554         sprite.dirty = true;
85555         this.renderItem(sprite);
85556     },
85557
85558     renderItem: function (sprite) {
85559         // Does the surface element exist?
85560         if (!this.el) {
85561             return;
85562         }
85563
85564         // Create sprite element if necessary
85565         if (!sprite.el) {
85566             this.createSpriteElement(sprite);
85567         }
85568
85569         if (sprite.dirty) {
85570             this.applyAttrs(sprite);
85571             if (sprite.dirtyTransform) {
85572                 this.applyTransformations(sprite);
85573             }
85574         }
85575     },
85576
85577     rotationCompensation: function (deg, dx, dy) {
85578         var matrix = Ext.create('Ext.draw.Matrix');
85579         matrix.rotate(-deg, 0.5, 0.5);
85580         return {
85581             x: matrix.x(dx, dy),
85582             y: matrix.y(dx, dy)
85583         };
85584     },
85585
85586     extractTransform: function (sprite) {
85587         var me = this,
85588             matrix = Ext.create('Ext.draw.Matrix'), scale,
85589             transformstions, tranformationsLength,
85590             transform, i = 0,
85591             shift = me.viewBoxShift;
85592
85593         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
85594             i < tranformationsLength; i ++) {
85595             transform = transformstions[i];
85596             switch (transform.type) {
85597                 case 'translate' :
85598                     matrix.translate(transform.x, transform.y);
85599                     break;
85600                 case 'rotate':
85601                     matrix.rotate(transform.degrees, transform.x, transform.y);
85602                     break;
85603                 case 'scale':
85604                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
85605                     break;
85606             }
85607         }
85608
85609         if (shift) {
85610             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
85611             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
85612         }
85613         
85614         return sprite.matrix = matrix;
85615     },
85616
85617     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
85618         var me = this,
85619             matrix = sprite.matrix,
85620             dom = sprite.el.dom,
85621             style = dom.style,
85622             yFlipper = 1,
85623             flip = "",
85624             fill = dom.getElementsByTagName('fill')[0],
85625             kx = me.zoom / sx,
85626             ky = me.zoom / sy,
85627             rotationCompensation;
85628         if (!sx || !sy) {
85629             return;
85630         }
85631         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
85632         style.rotation = rotate * (sx * sy < 0 ? -1 : 1);
85633         if (rotate) {
85634             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
85635             dx = rotationCompensation.x;
85636             dy = rotationCompensation.y;
85637         }
85638         if (sx < 0) {
85639             flip += "x"
85640         }
85641         if (sy < 0) {
85642             flip += " y";
85643             yFlipper = -1;
85644         }
85645         style.flip = flip;
85646         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
85647         if (fill) {
85648             dom.removeChild(fill);
85649             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
85650             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
85651             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
85652             dom.appendChild(fill);
85653         }
85654     },
85655
85656     transform : function (sprite) {
85657         var me = this,
85658             el = sprite.el,
85659             skew = sprite.skew,
85660             dom = el.dom,
85661             domStyle = dom.style,
85662             matrix = me.extractTransform(sprite).clone(),
85663             split, zoom = me.zoom,
85664             fill = dom.getElementsByTagName('fill')[0],
85665             isPatt = !String(sprite.fill).indexOf("url("),
85666             offset, c;
85667
85668
85669         // Hide element while we transform
85670
85671         if (sprite.type != "image" && skew && !isPatt) {
85672             // matrix transform via VML skew
85673             skew.matrix = matrix.toString();
85674             // skew.offset = '32767,1' OK
85675             // skew.offset = '32768,1' Crash
85676             // M$, R U kidding??
85677             offset = matrix.offset();
85678             if (offset[0] > 32767) {
85679                 offset[0] = 32767;
85680             } else if (offset[0] < -32768) {
85681                 offset[0] = -32768
85682             }
85683             if (offset[1] > 32767) {
85684                 offset[1] = 32767;
85685             } else if (offset[1] < -32768) {
85686                 offset[1] = -32768
85687             }
85688             skew.offset = offset;
85689         } else {
85690             if (skew) {
85691                 skew.matrix = "1 0 0 1";
85692                 skew.offset = "0 0";
85693             }
85694             split = matrix.split();
85695             if (split.isSimple) {
85696                 domStyle.filter = '';
85697                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
85698             } else {
85699                 domStyle.filter = matrix.toFilter();
85700                 var bb = me.getBBox(sprite),
85701                     dx = bb.x - sprite.x,
85702                     dy = bb.y - sprite.y;
85703                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
85704                 if (fill) {
85705                     dom.removeChild(fill);
85706                     fill.position = dx + ' ' + dy;
85707                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
85708                     dom.appendChild(fill);
85709                 }
85710             }
85711         }
85712     },
85713
85714     createItem: function (config) {
85715         return Ext.create('Ext.draw.Sprite', config);
85716     },
85717
85718     getRegion: function() {
85719         return this.el.getRegion();
85720     },
85721
85722     addCls: function(sprite, className) {
85723         if (sprite && sprite.el) {
85724             sprite.el.addCls(className);
85725         }
85726     },
85727
85728     removeCls: function(sprite, className) {
85729         if (sprite && sprite.el) {
85730             sprite.el.removeCls(className);
85731         }
85732     },
85733
85734     /**
85735      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
85736      * to its corresponding VML attributes and store it for later use by individual sprites.
85737      * @param {Object} gradient
85738      */
85739     addGradient: function(gradient) {
85740         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
85741             colors = [],
85742             stops = Ext.create('Ext.util.MixedCollection');
85743
85744         // Build colors string
85745         stops.addAll(gradient.stops);
85746         stops.sortByKey("ASC", function(a, b) {
85747             a = parseInt(a, 10);
85748             b = parseInt(b, 10);
85749             return a > b ? 1 : (a < b ? -1 : 0);
85750         });
85751         stops.eachKey(function(k, v) {
85752             colors.push(k + "% " + v.color);
85753         });
85754
85755         gradients.add(gradient.id, {
85756             colors: colors.join(","),
85757             angle: gradient.angle
85758         });
85759     },
85760
85761     destroy: function() {
85762         var me = this;
85763         
85764         me.callParent(arguments);
85765         if (me.el) {
85766             me.el.remove();
85767         }
85768         delete me.el;
85769     }
85770 });
85771
85772 /**
85773  * @class Ext.fx.target.ElementCSS
85774  * @extends Ext.fx.target.Element
85775  * 
85776  * This class represents a animation target for an {@link Ext.Element} that supports CSS
85777  * based animation. In general this class will not be created directly, the {@link Ext.Element} 
85778  * will be passed to the animation and the appropriate target will be created.
85779  */
85780 Ext.define('Ext.fx.target.ElementCSS', {
85781
85782     /* Begin Definitions */
85783
85784     extend: 'Ext.fx.target.Element',
85785
85786     /* End Definitions */
85787
85788     setAttr: function(targetData, isFirstFrame) {
85789         var cssArr = {
85790                 attrs: [],
85791                 duration: [],
85792                 easing: []
85793             },
85794             ln = targetData.length,
85795             attributes,
85796             attrs,
85797             attr,
85798             easing,
85799             duration,
85800             o,
85801             i,
85802             j,
85803             ln2;
85804         for (i = 0; i < ln; i++) {
85805             attrs = targetData[i];
85806             duration = attrs.duration;
85807             easing = attrs.easing;
85808             attrs = attrs.attrs;
85809             for (attr in attrs) {
85810                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
85811                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
85812                         return '-' + v.toLowerCase();
85813                     }));
85814                     cssArr.duration.push(duration + 'ms');
85815                     cssArr.easing.push(easing);
85816                 }
85817             }
85818         }
85819         attributes = cssArr.attrs.join(',');
85820         duration = cssArr.duration.join(',');
85821         easing = cssArr.easing.join(', ');
85822         for (i = 0; i < ln; i++) {
85823             attrs = targetData[i].attrs;
85824             for (attr in attrs) {
85825                 ln2 = attrs[attr].length;
85826                 for (j = 0; j < ln2; j++) {
85827                     o = attrs[attr][j];
85828                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
85829                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
85830                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
85831                     o[0].setStyle(attr, o[1]);
85832
85833                     // Must trigger reflow to make this get used as the start point for the transition that follows
85834                     if (isFirstFrame) {
85835                         o = o[0].dom.offsetWidth;
85836                     }
85837                     else {
85838                         // Remove transition properties when completed.
85839                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
85840                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
85841                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
85842                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
85843                         }, o[0], { single: true });
85844                     }
85845                 }
85846             }
85847         }
85848     }
85849 });
85850 /**
85851  * @class Ext.fx.target.CompositeElementCSS
85852  * @extends Ext.fx.target.CompositeElement
85853  * 
85854  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
85855  * constituent elements support CSS based animation. It allows each {@link Ext.Element} in 
85856  * the group to be animated as a whole. In general this class will not be created directly, 
85857  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
85858  * will be created.
85859  */
85860 Ext.define('Ext.fx.target.CompositeElementCSS', {
85861
85862     /* Begin Definitions */
85863
85864     extend: 'Ext.fx.target.CompositeElement',
85865
85866     requires: ['Ext.fx.target.ElementCSS'],
85867
85868     /* End Definitions */
85869     setAttr: function() {
85870         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
85871     }
85872 });
85873 /**
85874  * @class Ext.layout.container.AbstractFit
85875  * @extends Ext.layout.container.Container
85876  * @private
85877  */
85878 Ext.define('Ext.layout.container.AbstractFit', {
85879
85880     /* Begin Definitions */
85881
85882     extend: 'Ext.layout.container.Container',
85883
85884     /* End Definitions */
85885
85886     itemCls: Ext.baseCSSPrefix + 'fit-item',
85887     targetCls: Ext.baseCSSPrefix + 'layout-fit',
85888     type: 'fit'
85889 });
85890 /**
85891  * This is a base class for layouts that contain **a single item** that automatically expands to fill the layout's
85892  * container. This class is intended to be extended or created via the `layout: 'fit'`
85893  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
85894  *
85895  * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
85896  * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it. If the container has multiple
85897  * panels, only the first one will be displayed.
85898  *
85899  *     @example
85900  *     Ext.create('Ext.panel.Panel', {
85901  *         title: 'Fit Layout',
85902  *         width: 300,
85903  *         height: 150,
85904  *         layout:'fit',
85905  *         items: {
85906  *             title: 'Inner Panel',
85907  *             html: 'This is the inner panel content',
85908  *             bodyPadding: 20,
85909  *             border: false
85910  *         },
85911  *         renderTo: Ext.getBody()
85912  *     });
85913  */
85914 Ext.define('Ext.layout.container.Fit', {
85915
85916     /* Begin Definitions */
85917
85918     extend: 'Ext.layout.container.AbstractFit',
85919     alias: 'layout.fit',
85920     alternateClassName: 'Ext.layout.FitLayout',
85921     requires: ['Ext.layout.container.Box'],
85922
85923     /* End Definitions */
85924
85925     /**
85926      * @cfg {Object} defaultMargins
85927      * <p>If the individual contained items do not have a <tt>margins</tt>
85928      * property specified or margin specified via CSS, the default margins from this property will be
85929      * applied to each item.</p>
85930      * <br><p>This property may be specified as an object containing margins
85931      * to apply in the format:</p><pre><code>
85932 {
85933     top: (top margin),
85934     right: (right margin),
85935     bottom: (bottom margin),
85936     left: (left margin)
85937 }</code></pre>
85938      * <p>This property may also be specified as a string containing
85939      * space-separated, numeric margin values. The order of the sides associated
85940      * with each value matches the way CSS processes margin values:</p>
85941      * <div class="mdetail-params"><ul>
85942      * <li>If there is only one value, it applies to all sides.</li>
85943      * <li>If there are two values, the top and bottom borders are set to the
85944      * first value and the right and left are set to the second.</li>
85945      * <li>If there are three values, the top is set to the first value, the left
85946      * and right are set to the second, and the bottom is set to the third.</li>
85947      * <li>If there are four values, they apply to the top, right, bottom, and
85948      * left, respectively.</li>
85949      * </ul></div>
85950      * <p>Defaults to:</p><pre><code>
85951      * {top:0, right:0, bottom:0, left:0}
85952      * </code></pre>
85953      */
85954     defaultMargins: {
85955         top: 0,
85956         right: 0,
85957         bottom: 0,
85958         left: 0
85959     },
85960
85961     // @private
85962     onLayout : function() {
85963         var me = this,
85964             size,
85965             item,
85966             margins;
85967         me.callParent();
85968
85969         if (me.owner.items.length) {
85970             item = me.owner.items.get(0);
85971             margins = item.margins || me.defaultMargins;
85972             size = me.getLayoutTargetSize();
85973             size.width  -= margins.width;
85974             size.height -= margins.height;
85975             me.setItemBox(item, size);
85976
85977             // If any margins were configure either through the margins config, or in the CSS style,
85978             // Then positioning will be used.
85979             if (margins.left || margins.top) {
85980                 item.setPosition(margins.left, margins.top);
85981             }
85982         }
85983     },
85984
85985     getTargetBox : function() {
85986         return this.getLayoutTargetSize();
85987     },
85988
85989     setItemBox : function(item, box) {
85990         var me = this;
85991         if (item && box.height > 0) {
85992             if (!me.owner.isFixedWidth()) {
85993                box.width = undefined;
85994             }
85995             if (!me.owner.isFixedHeight()) {
85996                box.height = undefined;
85997             }
85998             me.setItemSize(item, box.width, box.height);
85999         }
86000     },
86001
86002     configureItem: function(item) {
86003
86004         // Card layout only controls dimensions which IT has controlled.
86005         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
86006         item.layoutManagedHeight = 0;
86007         item.layoutManagedWidth = 0;
86008
86009         this.callParent(arguments);
86010     }
86011 }, function() {
86012     // Use Box layout's renderItem which reads CSS margins, and adds them to any configured item margins
86013     // (Defaulting to "0 0 0 0")
86014     this.prototype.renderItem = Ext.layout.container.Box.prototype.renderItem;
86015 });
86016 /**
86017  * Abstract base class for {@link Ext.layout.container.Card Card layout}.
86018  * @private
86019  */
86020 Ext.define('Ext.layout.container.AbstractCard', {
86021
86022     /* Begin Definitions */
86023
86024     extend: 'Ext.layout.container.Fit',
86025
86026     /* End Definitions */
86027
86028     type: 'card',
86029
86030     sizeAllCards: false,
86031
86032     hideInactive: true,
86033
86034     /**
86035      * @cfg {Boolean} deferredRender
86036      * True to render each contained item at the time it becomes active, false to render all contained items
86037      * as soon as the layout is rendered.  If there is a significant amount of content or
86038      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
86039      * true might improve performance.
86040      */
86041     deferredRender : false,
86042
86043     beforeLayout: function() {
86044         var me = this;
86045         me.getActiveItem();
86046         if (me.activeItem && me.deferredRender) {
86047             me.renderItems([me.activeItem], me.getRenderTarget());
86048             return true;
86049         }
86050         else {
86051             return this.callParent(arguments);
86052         }
86053     },
86054
86055     renderChildren: function () {
86056         if (!this.deferredRender) {
86057             this.getActiveItem();
86058             this.callParent();
86059         }
86060     },
86061
86062     onLayout: function() {
86063         var me = this,
86064             activeItem = me.activeItem,
86065             items = me.getVisibleItems(),
86066             ln = items.length,
86067             targetBox = me.getTargetBox(),
86068             i, item;
86069
86070         for (i = 0; i < ln; i++) {
86071             item = items[i];
86072             me.setItemBox(item, targetBox);
86073         }
86074
86075         if (!me.firstActivated && activeItem) {
86076             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
86077                 activeItem.fireEvent('activate', activeItem);
86078             }
86079             me.firstActivated = true;
86080         }
86081     },
86082
86083     isValidParent : function(item, target, position) {
86084         // Note: Card layout does not care about order within the target because only one is ever visible.
86085         // We only care whether the item is a direct child of the target.
86086         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
86087         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
86088     },
86089
86090     /**
86091      * Return the active (visible) component in the layout.
86092      * @returns {Ext.Component}
86093      */
86094     getActiveItem: function() {
86095         var me = this;
86096         if (!me.activeItem && me.owner) {
86097             me.activeItem = me.parseActiveItem(me.owner.activeItem);
86098         }
86099
86100         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
86101             return me.activeItem;
86102         }
86103
86104         return null;
86105     },
86106
86107     // @private
86108     parseActiveItem: function(item) {
86109         if (item && item.isComponent) {
86110             return item;
86111         }
86112         else if (typeof item == 'number' || item === undefined) {
86113             return this.getLayoutItems()[item || 0];
86114         }
86115         else {
86116             return this.owner.getComponent(item);
86117         }
86118     },
86119
86120     // @private
86121     configureItem: function(item, position) {
86122         this.callParent([item, position]);
86123         if (this.hideInactive && this.activeItem !== item) {
86124             item.hide();
86125         }
86126         else {
86127             item.show();
86128         }
86129     },
86130
86131     onRemove: function(component) {
86132         if (component === this.activeItem) {
86133             this.activeItem = null;
86134             if (this.owner.items.getCount() === 0) {
86135                 this.firstActivated = false;
86136             }
86137         }
86138     },
86139
86140     // @private
86141     getAnimation: function(newCard, owner) {
86142         var newAnim = (newCard || {}).cardSwitchAnimation;
86143         if (newAnim === false) {
86144             return false;
86145         }
86146         return newAnim || owner.cardSwitchAnimation;
86147     },
86148
86149     /**
86150      * Return the active (visible) component in the layout to the next card
86151      * @returns {Ext.Component} The next component or false.
86152      */
86153     getNext: function() {
86154         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86155         //should come back in 4.1
86156         var wrap = arguments[0];
86157         var items = this.getLayoutItems(),
86158             index = Ext.Array.indexOf(items, this.activeItem);
86159         return items[index + 1] || (wrap ? items[0] : false);
86160     },
86161
86162     /**
86163      * Sets the active (visible) component in the layout to the next card
86164      * @return {Ext.Component} the activated component or false when nothing activated.
86165      */
86166     next: function() {
86167         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86168         //should come back in 4.1
86169         var anim = arguments[0], wrap = arguments[1];
86170         return this.setActiveItem(this.getNext(wrap), anim);
86171     },
86172
86173     /**
86174      * Return the active (visible) component in the layout to the previous card
86175      * @returns {Ext.Component} The previous component or false.
86176      */
86177     getPrev: function() {
86178         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86179         //should come back in 4.1
86180         var wrap = arguments[0];
86181         var items = this.getLayoutItems(),
86182             index = Ext.Array.indexOf(items, this.activeItem);
86183         return items[index - 1] || (wrap ? items[items.length - 1] : false);
86184     },
86185
86186     /**
86187      * Sets the active (visible) component in the layout to the previous card
86188      * @return {Ext.Component} the activated component or false when nothing activated.
86189      */
86190     prev: function() {
86191         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
86192         //should come back in 4.1
86193         var anim = arguments[0], wrap = arguments[1];
86194         return this.setActiveItem(this.getPrev(wrap), anim);
86195     }
86196 });
86197
86198 /**
86199  * Tracks what records are currently selected in a databound component.
86200  *
86201  * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
86202  * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
86203  * and provide a way to binding to the component.
86204  *
86205  * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
86206  * subclasses to update the UI widget.
86207  */
86208 Ext.define('Ext.selection.Model', {
86209     extend: 'Ext.util.Observable',
86210     alternateClassName: 'Ext.AbstractSelectionModel',
86211     requires: ['Ext.data.StoreManager'],
86212     // lastSelected
86213
86214     /**
86215      * @cfg {String} mode
86216      * Mode of selection.  Valid values are:
86217      *
86218      * - **SINGLE** - Only allows selecting one item at a time.  Use {@link #allowDeselect} to allow
86219      *   deselecting that item.  This is the default.
86220      * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
86221      *   select or deselect an item.
86222      * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
86223      */
86224
86225     /**
86226      * @cfg {Boolean} allowDeselect
86227      * Allow users to deselect a record in a DataView, List or Grid.
86228      * Only applicable when the {@link #mode} is 'SINGLE'.
86229      */
86230     allowDeselect: false,
86231
86232     /**
86233      * @property {Ext.util.MixedCollection} selected
86234      * A MixedCollection that maintains all of the currently selected records. Read-only.
86235      */
86236     selected: null,
86237
86238     /**
86239      * Prune records when they are removed from the store from the selection.
86240      * This is a private flag. For an example of its usage, take a look at
86241      * Ext.selection.TreeModel.
86242      * @private
86243      */
86244     pruneRemoved: true,
86245
86246     constructor: function(cfg) {
86247         var me = this;
86248
86249         cfg = cfg || {};
86250         Ext.apply(me, cfg);
86251
86252         me.addEvents(
86253             /**
86254              * @event
86255              * Fired after a selection change has occurred
86256              * @param {Ext.selection.Model} this
86257              * @param {Ext.data.Model[]} selected The selected records
86258              */
86259             'selectionchange'
86260         );
86261
86262         me.modes = {
86263             SINGLE: true,
86264             SIMPLE: true,
86265             MULTI: true
86266         };
86267
86268         // sets this.selectionMode
86269         me.setSelectionMode(cfg.mode || me.mode);
86270
86271         // maintains the currently selected records.
86272         me.selected = Ext.create('Ext.util.MixedCollection');
86273
86274         me.callParent(arguments);
86275     },
86276
86277     // binds the store to the selModel.
86278     bind : function(store, initial){
86279         var me = this;
86280
86281         if(!initial && me.store){
86282             if(store !== me.store && me.store.autoDestroy){
86283                 me.store.destroyStore();
86284             }else{
86285                 me.store.un("add", me.onStoreAdd, me);
86286                 me.store.un("clear", me.onStoreClear, me);
86287                 me.store.un("remove", me.onStoreRemove, me);
86288                 me.store.un("update", me.onStoreUpdate, me);
86289             }
86290         }
86291         if(store){
86292             store = Ext.data.StoreManager.lookup(store);
86293             store.on({
86294                 add: me.onStoreAdd,
86295                 clear: me.onStoreClear,
86296                 remove: me.onStoreRemove,
86297                 update: me.onStoreUpdate,
86298                 scope: me
86299             });
86300         }
86301         me.store = store;
86302         if(store && !initial) {
86303             me.refresh();
86304         }
86305     },
86306
86307     /**
86308      * Selects all records in the view.
86309      * @param {Boolean} suppressEvent True to suppress any select events
86310      */
86311     selectAll: function(suppressEvent) {
86312         var me = this,
86313             selections = me.store.getRange(),
86314             i = 0,
86315             len = selections.length,
86316             start = me.getSelection().length;
86317
86318         me.bulkChange = true;
86319         for (; i < len; i++) {
86320             me.doSelect(selections[i], true, suppressEvent);
86321         }
86322         delete me.bulkChange;
86323         // fire selection change only if the number of selections differs
86324         me.maybeFireSelectionChange(me.getSelection().length !== start);
86325     },
86326
86327     /**
86328      * Deselects all records in the view.
86329      * @param {Boolean} suppressEvent True to suppress any deselect events
86330      */
86331     deselectAll: function(suppressEvent) {
86332         var me = this,
86333             selections = me.getSelection(),
86334             i = 0,
86335             len = selections.length,
86336             start = me.getSelection().length;
86337
86338         me.bulkChange = true;
86339         for (; i < len; i++) {
86340             me.doDeselect(selections[i], suppressEvent);
86341         }
86342         delete me.bulkChange;
86343         // fire selection change only if the number of selections differs
86344         me.maybeFireSelectionChange(me.getSelection().length !== start);
86345     },
86346
86347     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
86348     // selection modes. Requires that an event be passed so that we can know
86349     // if user held ctrl or shift.
86350     selectWithEvent: function(record, e, keepExisting) {
86351         var me = this;
86352
86353         switch (me.selectionMode) {
86354             case 'MULTI':
86355                 if (e.ctrlKey && me.isSelected(record)) {
86356                     me.doDeselect(record, false);
86357                 } else if (e.shiftKey && me.lastFocused) {
86358                     me.selectRange(me.lastFocused, record, e.ctrlKey);
86359                 } else if (e.ctrlKey) {
86360                     me.doSelect(record, true, false);
86361                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
86362                     me.doSelect(record, keepExisting, false);
86363                 } else {
86364                     me.doSelect(record, false);
86365                 }
86366                 break;
86367             case 'SIMPLE':
86368                 if (me.isSelected(record)) {
86369                     me.doDeselect(record);
86370                 } else {
86371                     me.doSelect(record, true);
86372                 }
86373                 break;
86374             case 'SINGLE':
86375                 // if allowDeselect is on and this record isSelected, deselect it
86376                 if (me.allowDeselect && me.isSelected(record)) {
86377                     me.doDeselect(record);
86378                 // select the record and do NOT maintain existing selections
86379                 } else {
86380                     me.doSelect(record, false);
86381                 }
86382                 break;
86383         }
86384     },
86385
86386     /**
86387      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
86388      * All rows in between startRow and endRow are also selected.
86389      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
86390      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
86391      * @param {Boolean} [keepExisting] True to retain existing selections
86392      */
86393     selectRange : function(startRow, endRow, keepExisting, dir){
86394         var me = this,
86395             store = me.store,
86396             selectedCount = 0,
86397             i,
86398             tmp,
86399             dontDeselect,
86400             records = [];
86401
86402         if (me.isLocked()){
86403             return;
86404         }
86405
86406         if (!keepExisting) {
86407             me.deselectAll(true);
86408         }
86409
86410         if (!Ext.isNumber(startRow)) {
86411             startRow = store.indexOf(startRow);
86412         }
86413         if (!Ext.isNumber(endRow)) {
86414             endRow = store.indexOf(endRow);
86415         }
86416
86417         // swap values
86418         if (startRow > endRow){
86419             tmp = endRow;
86420             endRow = startRow;
86421             startRow = tmp;
86422         }
86423
86424         for (i = startRow; i <= endRow; i++) {
86425             if (me.isSelected(store.getAt(i))) {
86426                 selectedCount++;
86427             }
86428         }
86429
86430         if (!dir) {
86431             dontDeselect = -1;
86432         } else {
86433             dontDeselect = (dir == 'up') ? startRow : endRow;
86434         }
86435
86436         for (i = startRow; i <= endRow; i++){
86437             if (selectedCount == (endRow - startRow + 1)) {
86438                 if (i != dontDeselect) {
86439                     me.doDeselect(i, true);
86440                 }
86441             } else {
86442                 records.push(store.getAt(i));
86443             }
86444         }
86445         me.doMultiSelect(records, true);
86446     },
86447
86448     /**
86449      * Selects a record instance by record instance or index.
86450      * @param {Ext.data.Model[]/Number} records An array of records or an index
86451      * @param {Boolean} [keepExisting] True to retain existing selections
86452      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
86453      */
86454     select: function(records, keepExisting, suppressEvent) {
86455         // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
86456         if (Ext.isDefined(records)) {
86457             this.doSelect(records, keepExisting, suppressEvent);
86458         }
86459     },
86460
86461     /**
86462      * Deselects a record instance by record instance or index.
86463      * @param {Ext.data.Model[]/Number} records An array of records or an index
86464      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
86465      */
86466     deselect: function(records, suppressEvent) {
86467         this.doDeselect(records, suppressEvent);
86468     },
86469
86470     doSelect: function(records, keepExisting, suppressEvent) {
86471         var me = this,
86472             record;
86473
86474         if (me.locked) {
86475             return;
86476         }
86477         if (typeof records === "number") {
86478             records = [me.store.getAt(records)];
86479         }
86480         if (me.selectionMode == "SINGLE" && records) {
86481             record = records.length ? records[0] : records;
86482             me.doSingleSelect(record, suppressEvent);
86483         } else {
86484             me.doMultiSelect(records, keepExisting, suppressEvent);
86485         }
86486     },
86487
86488     doMultiSelect: function(records, keepExisting, suppressEvent) {
86489         var me = this,
86490             selected = me.selected,
86491             change = false,
86492             i = 0,
86493             len, record;
86494
86495         if (me.locked) {
86496             return;
86497         }
86498
86499
86500         records = !Ext.isArray(records) ? [records] : records;
86501         len = records.length;
86502         if (!keepExisting && selected.getCount() > 0) {
86503             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
86504                 return;
86505             }
86506             // TODO - coalesce the selectionchange event in deselect w/the one below...
86507         }
86508
86509         function commit () {
86510             selected.add(record);
86511             change = true;
86512         }
86513
86514         for (; i < len; i++) {
86515             record = records[i];
86516             if (keepExisting && me.isSelected(record)) {
86517                 continue;
86518             }
86519             me.lastSelected = record;
86520
86521             me.onSelectChange(record, true, suppressEvent, commit);
86522         }
86523         me.setLastFocused(record, suppressEvent);
86524         // fire selchange if there was a change and there is no suppressEvent flag
86525         me.maybeFireSelectionChange(change && !suppressEvent);
86526     },
86527
86528     // records can be an index, a record or an array of records
86529     doDeselect: function(records, suppressEvent) {
86530         var me = this,
86531             selected = me.selected,
86532             i = 0,
86533             len, record,
86534             attempted = 0,
86535             accepted = 0;
86536
86537         if (me.locked) {
86538             return false;
86539         }
86540
86541         if (typeof records === "number") {
86542             records = [me.store.getAt(records)];
86543         } else if (!Ext.isArray(records)) {
86544             records = [records];
86545         }
86546
86547         function commit () {
86548             ++accepted;
86549             selected.remove(record);
86550         }
86551
86552         len = records.length;
86553
86554         for (; i < len; i++) {
86555             record = records[i];
86556             if (me.isSelected(record)) {
86557                 if (me.lastSelected == record) {
86558                     me.lastSelected = selected.last();
86559                 }
86560                 ++attempted;
86561                 me.onSelectChange(record, false, suppressEvent, commit);
86562             }
86563         }
86564
86565         // fire selchange if there was a change and there is no suppressEvent flag
86566         me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
86567         return accepted === attempted;
86568     },
86569
86570     doSingleSelect: function(record, suppressEvent) {
86571         var me = this,
86572             changed = false,
86573             selected = me.selected;
86574
86575         if (me.locked) {
86576             return;
86577         }
86578         // already selected.
86579         // should we also check beforeselect?
86580         if (me.isSelected(record)) {
86581             return;
86582         }
86583
86584         function commit () {
86585             me.bulkChange = true;
86586             if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
86587                 delete me.bulkChange;
86588                 return false;
86589             }
86590             delete me.bulkChange;
86591
86592             selected.add(record);
86593             me.lastSelected = record;
86594             changed = true;
86595         }
86596
86597         me.onSelectChange(record, true, suppressEvent, commit);
86598
86599         if (changed) {
86600             if (!suppressEvent) {
86601                 me.setLastFocused(record);
86602             }
86603             me.maybeFireSelectionChange(!suppressEvent);
86604         }
86605     },
86606
86607     /**
86608      * Sets a record as the last focused record. This does NOT mean
86609      * that the record has been selected.
86610      * @param {Ext.data.Model} record
86611      */
86612     setLastFocused: function(record, supressFocus) {
86613         var me = this,
86614             recordBeforeLast = me.lastFocused;
86615         me.lastFocused = record;
86616         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
86617     },
86618
86619     /**
86620      * Determines if this record is currently focused.
86621      * @param {Ext.data.Model} record
86622      */
86623     isFocused: function(record) {
86624         return record === this.getLastFocused();
86625     },
86626
86627
86628     // fire selection change as long as true is not passed
86629     // into maybeFireSelectionChange
86630     maybeFireSelectionChange: function(fireEvent) {
86631         var me = this;
86632         if (fireEvent && !me.bulkChange) {
86633             me.fireEvent('selectionchange', me, me.getSelection());
86634         }
86635     },
86636
86637     /**
86638      * Returns the last selected record.
86639      */
86640     getLastSelected: function() {
86641         return this.lastSelected;
86642     },
86643
86644     getLastFocused: function() {
86645         return this.lastFocused;
86646     },
86647
86648     /**
86649      * Returns an array of the currently selected records.
86650      * @return {Ext.data.Model[]} The selected records
86651      */
86652     getSelection: function() {
86653         return this.selected.getRange();
86654     },
86655
86656     /**
86657      * Returns the current selectionMode.
86658      * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
86659      */
86660     getSelectionMode: function() {
86661         return this.selectionMode;
86662     },
86663
86664     /**
86665      * Sets the current selectionMode.
86666      * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
86667      */
86668     setSelectionMode: function(selMode) {
86669         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
86670         // set to mode specified unless it doesnt exist, in that case
86671         // use single.
86672         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
86673     },
86674
86675     /**
86676      * Returns true if the selections are locked.
86677      * @return {Boolean}
86678      */
86679     isLocked: function() {
86680         return this.locked;
86681     },
86682
86683     /**
86684      * Locks the current selection and disables any changes from happening to the selection.
86685      * @param {Boolean} locked  True to lock, false to unlock.
86686      */
86687     setLocked: function(locked) {
86688         this.locked = !!locked;
86689     },
86690
86691     /**
86692      * Returns true if the specified row is selected.
86693      * @param {Ext.data.Model/Number} record The record or index of the record to check
86694      * @return {Boolean}
86695      */
86696     isSelected: function(record) {
86697         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
86698         return this.selected.indexOf(record) !== -1;
86699     },
86700
86701     /**
86702      * Returns true if there are any a selected records.
86703      * @return {Boolean}
86704      */
86705     hasSelection: function() {
86706         return this.selected.getCount() > 0;
86707     },
86708
86709     refresh: function() {
86710         var me = this,
86711             toBeSelected = [],
86712             oldSelections = me.getSelection(),
86713             len = oldSelections.length,
86714             selection,
86715             change,
86716             i = 0,
86717             lastFocused = this.getLastFocused();
86718
86719         // check to make sure that there are no records
86720         // missing after the refresh was triggered, prune
86721         // them from what is to be selected if so
86722         for (; i < len; i++) {
86723             selection = oldSelections[i];
86724             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
86725                 toBeSelected.push(selection);
86726             }
86727         }
86728
86729         // there was a change from the old selected and
86730         // the new selection
86731         if (me.selected.getCount() != toBeSelected.length) {
86732             change = true;
86733         }
86734
86735         me.clearSelections();
86736
86737         if (me.store.indexOf(lastFocused) !== -1) {
86738             // restore the last focus but supress restoring focus
86739             this.setLastFocused(lastFocused, true);
86740         }
86741
86742         if (toBeSelected.length) {
86743             // perform the selection again
86744             me.doSelect(toBeSelected, false, true);
86745         }
86746
86747         me.maybeFireSelectionChange(change);
86748     },
86749
86750     /**
86751      * A fast reset of the selections without firing events, updating the ui, etc.
86752      * For private usage only.
86753      * @private
86754      */
86755     clearSelections: function() {
86756         // reset the entire selection to nothing
86757         this.selected.clear();
86758         this.lastSelected = null;
86759         this.setLastFocused(null);
86760     },
86761
86762     // when a record is added to a store
86763     onStoreAdd: function() {
86764
86765     },
86766
86767     // when a store is cleared remove all selections
86768     // (if there were any)
86769     onStoreClear: function() {
86770         if (this.selected.getCount > 0) {
86771             this.clearSelections();
86772             this.maybeFireSelectionChange(true);
86773         }
86774     },
86775
86776     // prune records from the SelectionModel if
86777     // they were selected at the time they were
86778     // removed.
86779     onStoreRemove: function(store, record) {
86780         var me = this,
86781             selected = me.selected;
86782
86783         if (me.locked || !me.pruneRemoved) {
86784             return;
86785         }
86786
86787         if (selected.remove(record)) {
86788             if (me.lastSelected == record) {
86789                 me.lastSelected = null;
86790             }
86791             if (me.getLastFocused() == record) {
86792                 me.setLastFocused(null);
86793             }
86794             me.maybeFireSelectionChange(true);
86795         }
86796     },
86797
86798     /**
86799      * Returns the count of selected records.
86800      * @return {Number} The number of selected records
86801      */
86802     getCount: function() {
86803         return this.selected.getCount();
86804     },
86805
86806     // cleanup.
86807     destroy: function() {
86808
86809     },
86810
86811     // if records are updated
86812     onStoreUpdate: function() {
86813
86814     },
86815
86816     // @abstract
86817     onSelectChange: function(record, isSelected, suppressEvent) {
86818
86819     },
86820
86821     // @abstract
86822     onLastFocusChanged: function(oldFocused, newFocused) {
86823
86824     },
86825
86826     // @abstract
86827     onEditorKey: function(field, e) {
86828
86829     },
86830
86831     // @abstract
86832     bindComponent: function(cmp) {
86833
86834     }
86835 });
86836 /**
86837  * @class Ext.selection.DataViewModel
86838  * @ignore
86839  */
86840 Ext.define('Ext.selection.DataViewModel', {
86841     extend: 'Ext.selection.Model',
86842
86843     requires: ['Ext.util.KeyNav'],
86844
86845     deselectOnContainerClick: true,
86846
86847     /**
86848      * @cfg {Boolean} enableKeyNav
86849      *
86850      * Turns on/off keyboard navigation within the DataView.
86851      */
86852     enableKeyNav: true,
86853
86854     constructor: function(cfg){
86855         this.addEvents(
86856             /**
86857              * @event beforedeselect
86858              * Fired before a record is deselected. If any listener returns false, the
86859              * deselection is cancelled.
86860              * @param {Ext.selection.DataViewModel} this
86861              * @param {Ext.data.Model} record The deselected record
86862              */
86863             'beforedeselect',
86864
86865             /**
86866              * @event beforeselect
86867              * Fired before a record is selected. If any listener returns false, the
86868              * selection is cancelled.
86869              * @param {Ext.selection.DataViewModel} this
86870              * @param {Ext.data.Model} record The selected record
86871              */
86872             'beforeselect',
86873
86874             /**
86875              * @event deselect
86876              * Fired after a record is deselected
86877              * @param {Ext.selection.DataViewModel} this
86878              * @param  {Ext.data.Model} record The deselected record
86879              */
86880             'deselect',
86881
86882             /**
86883              * @event select
86884              * Fired after a record is selected
86885              * @param {Ext.selection.DataViewModel} this
86886              * @param  {Ext.data.Model} record The selected record
86887              */
86888             'select'
86889         );
86890         this.callParent(arguments);
86891     },
86892
86893     bindComponent: function(view) {
86894         var me = this,
86895             eventListeners = {
86896                 refresh: me.refresh,
86897                 scope: me
86898             };
86899
86900         me.view = view;
86901         me.bind(view.getStore());
86902
86903         view.on(view.triggerEvent, me.onItemClick, me);
86904         view.on(view.triggerCtEvent, me.onContainerClick, me);
86905
86906         view.on(eventListeners);
86907
86908         if (me.enableKeyNav) {
86909             me.initKeyNav(view);
86910         }
86911     },
86912
86913     onItemClick: function(view, record, item, index, e) {
86914         this.selectWithEvent(record, e);
86915     },
86916
86917     onContainerClick: function() {
86918         if (this.deselectOnContainerClick) {
86919             this.deselectAll();
86920         }
86921     },
86922
86923     initKeyNav: function(view) {
86924         var me = this;
86925
86926         if (!view.rendered) {
86927             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
86928             return;
86929         }
86930
86931         view.el.set({
86932             tabIndex: -1
86933         });
86934         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
86935             down: Ext.pass(me.onNavKey, [1], me),
86936             right: Ext.pass(me.onNavKey, [1], me),
86937             left: Ext.pass(me.onNavKey, [-1], me),
86938             up: Ext.pass(me.onNavKey, [-1], me),
86939             scope: me
86940         });
86941     },
86942
86943     onNavKey: function(step) {
86944         step = step || 1;
86945         var me = this,
86946             view = me.view,
86947             selected = me.getSelection()[0],
86948             numRecords = me.view.store.getCount(),
86949             idx;
86950
86951         if (selected) {
86952             idx = view.indexOf(view.getNode(selected)) + step;
86953         } else {
86954             idx = 0;
86955         }
86956
86957         if (idx < 0) {
86958             idx = numRecords - 1;
86959         } else if (idx >= numRecords) {
86960             idx = 0;
86961         }
86962
86963         me.select(idx);
86964     },
86965
86966     // Allow the DataView to update the ui
86967     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
86968         var me = this,
86969             view = me.view,
86970             eventName = isSelected ? 'select' : 'deselect';
86971
86972         if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
86973                 commitFn() !== false) {
86974
86975             if (isSelected) {
86976                 view.onItemSelect(record);
86977             } else {
86978                 view.onItemDeselect(record);
86979             }
86980
86981             if (!suppressEvent) {
86982                 me.fireEvent(eventName, me, record);
86983             }
86984         }
86985     },
86986     
86987     destroy: function(){
86988         Ext.destroy(this.keyNav);
86989         this.callParent();
86990     }
86991 });
86992
86993 /**
86994  * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
86995  * options, such as:
86996  *
86997  * - {@link #path}
86998  * - {@link #expires}
86999  * - {@link #domain}
87000  * - {@link #secure}
87001  *
87002  * Example:
87003  *
87004  *     Ext.create('Ext.state.CookieProvider', {
87005  *         path: "/cgi-bin/",
87006  *         expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
87007  *         domain: "sencha.com"
87008  *     });
87009  *
87010  *     Ext.state.Manager.setProvider(cp);
87011  *
87012  * @constructor
87013  * Creates a new CookieProvider.
87014  * @param {Object} config (optional) Config object.
87015  * @return {Object}
87016  */
87017 Ext.define('Ext.state.CookieProvider', {
87018     extend: 'Ext.state.Provider',
87019
87020     /**
87021      * @cfg {String} path
87022      * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
87023      */
87024
87025     /**
87026      * @cfg {Date} expires
87027      * The cookie expiration date. Defaults to 7 days from now.
87028      */
87029
87030     /**
87031      * @cfg {String} domain
87032      * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
87033      * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
87034      * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
87035      * the 'www' like 'www.sencha.com'.
87036      */
87037
87038     /**
87039      * @cfg {Boolean} [secure=false]
87040      * True if the site is using SSL
87041      */
87042
87043     /**
87044      * Creates a new CookieProvider.
87045      * @param {Object} [config] Config object.
87046      */
87047     constructor : function(config){
87048         var me = this;
87049         me.path = "/";
87050         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
87051         me.domain = null;
87052         me.secure = false;
87053         me.callParent(arguments);
87054         me.state = me.readCookies();
87055     },
87056
87057     // private
87058     set : function(name, value){
87059         var me = this;
87060
87061         if(typeof value == "undefined" || value === null){
87062             me.clear(name);
87063             return;
87064         }
87065         me.setCookie(name, value);
87066         me.callParent(arguments);
87067     },
87068
87069     // private
87070     clear : function(name){
87071         this.clearCookie(name);
87072         this.callParent(arguments);
87073     },
87074
87075     // private
87076     readCookies : function(){
87077         var cookies = {},
87078             c = document.cookie + ";",
87079             re = /\s?(.*?)=(.*?);/g,
87080             prefix = this.prefix,
87081             len = prefix.length,
87082             matches,
87083             name,
87084             value;
87085
87086         while((matches = re.exec(c)) != null){
87087             name = matches[1];
87088             value = matches[2];
87089             if (name && name.substring(0, len) == prefix){
87090                 cookies[name.substr(len)] = this.decodeValue(value);
87091             }
87092         }
87093         return cookies;
87094     },
87095
87096     // private
87097     setCookie : function(name, value){
87098         var me = this;
87099
87100         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
87101            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
87102            ((me.path == null) ? "" : ("; path=" + me.path)) +
87103            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
87104            ((me.secure == true) ? "; secure" : "");
87105     },
87106
87107     // private
87108     clearCookie : function(name){
87109         var me = this;
87110
87111         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
87112            ((me.path == null) ? "" : ("; path=" + me.path)) +
87113            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
87114            ((me.secure == true) ? "; secure" : "");
87115     }
87116 });
87117
87118 /**
87119  * @class Ext.state.LocalStorageProvider
87120  * @extends Ext.state.Provider
87121  * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
87122  * If the browser does not support local storage, an exception will be thrown upon instantiating
87123  * this class.
87124  */
87125
87126 Ext.define('Ext.state.LocalStorageProvider', {
87127     /* Begin Definitions */
87128     
87129     extend: 'Ext.state.Provider',
87130     
87131     alias: 'state.localstorage',
87132     
87133     /* End Definitions */
87134    
87135     constructor: function(){
87136         var me = this;
87137         me.callParent(arguments);
87138         me.store = me.getStorageObject();
87139         me.state = me.readLocalStorage();
87140     },
87141     
87142     readLocalStorage: function(){
87143         var store = this.store,
87144             i = 0,
87145             len = store.length,
87146             prefix = this.prefix,
87147             prefixLen = prefix.length,
87148             data = {},
87149             key;
87150             
87151         for (; i < len; ++i) {
87152             key = store.key(i);
87153             if (key.substring(0, prefixLen) == prefix) {
87154                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
87155             }            
87156         }
87157         return data;
87158     },
87159     
87160     set : function(name, value){
87161         var me = this;
87162         
87163         me.clear(name);
87164         if (typeof value == "undefined" || value === null) {
87165             return;
87166         }
87167         me.store.setItem(me.prefix + name, me.encodeValue(value));
87168         me.callParent(arguments);
87169     },
87170
87171     // private
87172     clear : function(name){
87173         this.store.removeItem(this.prefix + name);
87174         this.callParent(arguments);
87175     },
87176     
87177     getStorageObject: function(){
87178         try {
87179             var supports = 'localStorage' in window && window['localStorage'] !== null;
87180             if (supports) {
87181                 return window.localStorage;
87182             }
87183         } catch (e) {
87184             return false;
87185         }
87186         Ext.Error.raise('LocalStorage is not supported by the current browser');
87187     }    
87188 });
87189
87190 /**
87191  * Represents a 2D point with x and y properties, useful for comparison and instantiation
87192  * from an event:
87193  *
87194  *     var point = Ext.util.Point.fromEvent(e);
87195  *
87196  */
87197 Ext.define('Ext.util.Point', {
87198
87199     /* Begin Definitions */
87200     extend: 'Ext.util.Region',
87201
87202     statics: {
87203
87204         /**
87205          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
87206          * @static
87207          * @param {Event} e The event
87208          * @return {Ext.util.Point}
87209          */
87210         fromEvent: function(e) {
87211             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
87212             return new this(e.pageX, e.pageY);
87213         }
87214     },
87215
87216     /* End Definitions */
87217
87218     /**
87219      * Creates a point from two coordinates.
87220      * @param {Number} x X coordinate.
87221      * @param {Number} y Y coordinate.
87222      */
87223     constructor: function(x, y) {
87224         this.callParent([y, x, y, x]);
87225     },
87226
87227     /**
87228      * Returns a human-eye-friendly string that represents this point,
87229      * useful for debugging
87230      * @return {String}
87231      */
87232     toString: function() {
87233         return "Point[" + this.x + "," + this.y + "]";
87234     },
87235
87236     /**
87237      * Compare this point and another point
87238      * @param {Ext.util.Point/Object} The point to compare with, either an instance
87239      * of Ext.util.Point or an object with left and top properties
87240      * @return {Boolean} Returns whether they are equivalent
87241      */
87242     equals: function(p) {
87243         return (this.x == p.x && this.y == p.y);
87244     },
87245
87246     /**
87247      * Whether the given point is not away from this point within the given threshold amount.
87248      * @param {Ext.util.Point/Object} p The point to check with, either an instance
87249      * of Ext.util.Point or an object with left and top properties
87250      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
87251      * @return {Boolean}
87252      */
87253     isWithin: function(p, threshold) {
87254         if (!Ext.isObject(threshold)) {
87255             threshold = {
87256                 x: threshold,
87257                 y: threshold
87258             };
87259         }
87260
87261         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
87262                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
87263     },
87264
87265     /**
87266      * Compare this point with another point when the x and y values of both points are rounded. E.g:
87267      * [100.3,199.8] will equals to [100, 200]
87268      * @param {Ext.util.Point/Object} p The point to compare with, either an instance
87269      * of Ext.util.Point or an object with x and y properties
87270      * @return {Boolean}
87271      */
87272     roundedEquals: function(p) {
87273         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
87274     }
87275 }, function() {
87276     /**
87277      * @method
87278      * Alias for {@link #translateBy}
87279      * @alias Ext.util.Region#translateBy
87280      */
87281     this.prototype.translate = Ext.util.Region.prototype.translateBy;
87282 });
87283
87284 /**
87285  * @class Ext.LoadMask
87286  * <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
87287  * {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
87288  * be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
87289  * <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
87290  * the Store's loading process.</p>
87291  * <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
87292  * object, and upon show, it will place itsef at the top of the hierarchy.</p>
87293  * <p>Example usage:</p>
87294  * <pre><code>
87295 // Basic mask:
87296 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
87297 myMask.show();
87298 </code></pre>
87299
87300  */
87301
87302 Ext.define('Ext.LoadMask', {
87303
87304     extend: 'Ext.Component',
87305
87306     alias: 'widget.loadmask',
87307
87308     /* Begin Definitions */
87309
87310     mixins: {
87311         floating: 'Ext.util.Floating'
87312     },
87313
87314     uses: ['Ext.data.StoreManager'],
87315
87316     /* End Definitions */
87317
87318     /**
87319      * @cfg {Ext.data.Store} store
87320      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
87321      * hidden on either load success, or load fail.
87322      */
87323
87324     /**
87325      * @cfg {String} msg
87326      * The text to display in a centered loading message box.
87327      */
87328     msg : 'Loading...',
87329     /**
87330      * @cfg {String} [msgCls="x-mask-loading"]
87331      * The CSS class to apply to the loading message element.
87332      */
87333     msgCls : Ext.baseCSSPrefix + 'mask-loading',
87334     
87335     /**
87336      * @cfg {Boolean} useMsg
87337      * Whether or not to use a loading message class or simply mask the bound element.
87338      */
87339     useMsg: true,
87340
87341     /**
87342      * Read-only. True if the mask is currently disabled so that it will not be displayed
87343      * @type Boolean
87344      */
87345     disabled: false,
87346
87347     baseCls: Ext.baseCSSPrefix + 'mask-msg',
87348
87349     renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
87350
87351     // Private. The whole point is that there's a mask.
87352     modal: true,
87353
87354     // Private. Obviously, it's floating.
87355     floating: {
87356         shadow: 'frame'
87357     },
87358
87359     // Private. Masks are not focusable
87360     focusOnToFront: false,
87361
87362     /**
87363      * Creates new LoadMask.
87364      * @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
87365      * <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
87366      * the mask will be automatically sized upon Component resize, the message box will be kept centered,
87367      * and the mask only be visible when the Component is.</p>
87368      * @param {Object} [config] The config object
87369      */
87370     constructor : function(el, config) {
87371         var me = this;
87372
87373         // If a Component passed, bind to it.
87374         if (el.isComponent) {
87375             me.ownerCt = el;
87376             me.bindComponent(el);
87377         }
87378         // Create a dumy Component encapsulating the specified Element
87379         else {
87380             me.ownerCt = new Ext.Component({
87381                 el: Ext.get(el),
87382                 rendered: true,
87383                 componentLayoutCounter: 1
87384             });
87385             me.container = el;
87386         }
87387         me.callParent([config]);
87388
87389         if (me.store) {
87390             me.bindStore(me.store, true);
87391         }
87392         me.renderData = {
87393             msgCls: me.msgCls
87394         };
87395         me.renderSelectors = {
87396             msgEl: 'div'
87397         };
87398     },
87399
87400     bindComponent: function(comp) {
87401         this.mon(comp, {
87402             resize: this.onComponentResize,
87403             scope: this
87404         });
87405     },
87406
87407     afterRender: function() {
87408         this.callParent(arguments);
87409         this.container = this.floatParent.getContentTarget();
87410     },
87411
87412     /**
87413      * @private
87414      * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
87415      */
87416     onComponentResize: function() {
87417         var me = this;
87418         if (me.rendered && me.isVisible()) {
87419             me.toFront();
87420             me.center();
87421         }
87422     },
87423
87424     /**
87425      * Changes the data store bound to this LoadMask.
87426      * @param {Ext.data.Store} store The store to bind to this LoadMask
87427      */
87428     bindStore : function(store, initial) {
87429         var me = this;
87430
87431         if (!initial && me.store) {
87432             me.mun(me.store, {
87433                 scope: me,
87434                 beforeload: me.onBeforeLoad,
87435                 load: me.onLoad,
87436                 exception: me.onLoad
87437             });
87438             if (!store) {
87439                 me.store = null;
87440             }
87441         }
87442         if (store) {
87443             store = Ext.data.StoreManager.lookup(store);
87444             me.mon(store, {
87445                 scope: me,
87446                 beforeload: me.onBeforeLoad,
87447                 load: me.onLoad,
87448                 exception: me.onLoad
87449             });
87450
87451         }
87452         me.store = store;
87453         if (store && store.isLoading()) {
87454             me.onBeforeLoad();
87455         }
87456     },
87457
87458     onDisable : function() {
87459         this.callParent(arguments);
87460         if (this.loading) {
87461             this.onLoad();
87462         }
87463     },
87464
87465     // private
87466     onBeforeLoad : function() {
87467         var me = this,
87468             owner = me.ownerCt || me.floatParent,
87469             origin;
87470         if (!this.disabled) {
87471             // If the owning Component has not been layed out, defer so that the ZIndexManager
87472             // gets to read its layed out size when sizing the modal mask
87473             if (owner.componentLayoutCounter) {
87474                 Ext.Component.prototype.show.call(me);
87475             } else {
87476                 // The code below is a 'run-once' interceptor.
87477                 origin = owner.afterComponentLayout;
87478                 owner.afterComponentLayout = function() {
87479                     owner.afterComponentLayout = origin;
87480                     origin.apply(owner, arguments);
87481                     if(me.loading) {
87482                         Ext.Component.prototype.show.call(me);
87483                     }
87484                 };
87485             }
87486         }
87487     },
87488
87489     onHide: function(){
87490         var me = this;
87491         me.callParent(arguments);
87492         me.showOnParentShow = true;
87493     },
87494
87495     onShow: function() {
87496         var me = this,
87497             msgEl = me.msgEl;
87498             
87499         me.callParent(arguments);
87500         me.loading = true;
87501         if (me.useMsg) {
87502             msgEl.show().update(me.msg);
87503         } else {
87504             msgEl.parent().hide();
87505         }
87506     },
87507
87508     afterShow: function() {
87509         this.callParent(arguments);
87510         this.center();
87511     },
87512
87513     // private
87514     onLoad : function() {
87515         this.loading = false;
87516         Ext.Component.prototype.hide.call(this);
87517     }
87518 });
87519 /**
87520  * @class Ext.view.AbstractView
87521  * @extends Ext.Component
87522  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
87523  * @private
87524  */
87525 Ext.define('Ext.view.AbstractView', {
87526     extend: 'Ext.Component',
87527     alternateClassName: 'Ext.view.AbstractView',
87528     requires: [
87529         'Ext.LoadMask',
87530         'Ext.data.StoreManager',
87531         'Ext.CompositeElementLite',
87532         'Ext.DomQuery',
87533         'Ext.selection.DataViewModel'
87534     ],
87535
87536     inheritableStatics: {
87537         getRecord: function(node) {
87538             return this.getBoundView(node).getRecord(node);
87539         },
87540
87541         getBoundView: function(node) {
87542             return Ext.getCmp(node.boundView);
87543         }
87544     },
87545
87546     /**
87547      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
87548      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
87549      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
87550      */
87551     /**
87552      * @cfg {Ext.data.Store} store (required)
87553      * The {@link Ext.data.Store} to bind this DataView to.
87554      */
87555
87556     /**
87557      * @cfg {Boolean} deferInitialRefresh
87558      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
87559      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
87560      * by the expensive update of the view structure.</p>
87561      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
87562      * <i>selection</i> may not take place at render time. To access a View's item elements as soon as possible, use the {@link #viewready} event.
87563      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
87564      */
87565     deferInitialRefresh: true,
87566
87567     /**
87568      * @cfg {String} itemSelector (required)
87569      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
87570      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
87571      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
87572      * only be one root level element that matches the selector for each record.
87573      */
87574
87575     /**
87576      * @cfg {String} itemCls
87577      * Specifies the class to be assigned to each element in the view when used in conjunction with the
87578      * {@link #itemTpl} configuration.
87579      */
87580     itemCls: Ext.baseCSSPrefix + 'dataview-item',
87581
87582     /**
87583      * @cfg {String/String[]/Ext.XTemplate} itemTpl
87584      * The inner portion of the item template to be rendered. Follows an XTemplate
87585      * structure and will be placed inside of a tpl.
87586      */
87587
87588     /**
87589      * @cfg {String} overItemCls
87590      * A CSS class to apply to each item in the view on mouseover.
87591      * Ensure {@link #trackOver} is set to `true` to make use of this.
87592      */
87593
87594     /**
87595      * @cfg {String} loadingText
87596      * A string to display during data load operations.  If specified, this text will be
87597      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
87598      * contents will continue to display normally until the new data is loaded and the contents are replaced.
87599      */
87600     loadingText: 'Loading...',
87601
87602     /**
87603      * @cfg {Boolean/Object} loadMask
87604      * False to disable a load mask from displaying will the view is loading. This can also be a
87605      * {@link Ext.LoadMask} configuration object.
87606      */
87607     loadMask: true,
87608
87609     /**
87610      * @cfg {String} loadingCls
87611      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
87612      */
87613
87614     /**
87615      * @cfg {Boolean} loadingUseMsg
87616      * Whether or not to use the loading message.
87617      * @private
87618      */
87619     loadingUseMsg: true,
87620
87621
87622     /**
87623      * @cfg {Number} loadingHeight
87624      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
87625      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
87626      * loading mask is applied and there are no other contents in the data view.
87627      */
87628
87629     /**
87630      * @cfg {String} [selectedItemCls='x-view-selected']
87631      * A CSS class to apply to each selected item in the view.
87632      */
87633     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
87634
87635     /**
87636      * @cfg {String} emptyText
87637      * The text to display in the view when there is no data to display.
87638      * Note that when using local data the emptyText will not be displayed unless you set
87639      * the {@link #deferEmptyText} option to false.
87640      */
87641     emptyText: "",
87642
87643     /**
87644      * @cfg {Boolean} deferEmptyText
87645      * True to defer emptyText being applied until the store's first load.
87646      */
87647     deferEmptyText: true,
87648
87649     /**
87650      * @cfg {Boolean} trackOver
87651      * True to enable mouseenter and mouseleave events
87652      */
87653     trackOver: false,
87654
87655     /**
87656      * @cfg {Boolean} blockRefresh
87657      * Set this to true to ignore datachanged events on the bound store. This is useful if
87658      * you wish to provide custom transition animations via a plugin
87659      */
87660     blockRefresh: false,
87661
87662     /**
87663      * @cfg {Boolean} disableSelection
87664      * True to disable selection within the DataView. This configuration will lock the selection model
87665      * that the DataView uses.
87666      */
87667
87668
87669     //private
87670     last: false,
87671
87672     triggerEvent: 'itemclick',
87673     triggerCtEvent: 'containerclick',
87674
87675     addCmpEvents: function() {
87676
87677     },
87678
87679     // private
87680     initComponent : function(){
87681         var me = this,
87682             isDef = Ext.isDefined,
87683             itemTpl = me.itemTpl,
87684             memberFn = {};
87685
87686         if (itemTpl) {
87687             if (Ext.isArray(itemTpl)) {
87688                 // string array
87689                 itemTpl = itemTpl.join('');
87690             } else if (Ext.isObject(itemTpl)) {
87691                 // tpl instance
87692                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
87693                 itemTpl = itemTpl.html;
87694             }
87695
87696             if (!me.itemSelector) {
87697                 me.itemSelector = '.' + me.itemCls;
87698             }
87699
87700             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
87701             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
87702         }
87703
87704         if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
87705             Ext.Error.raise({
87706                 sourceClass: 'Ext.view.View',
87707                 tpl: me.tpl,
87708                 itemSelector: me.itemSelector,
87709                 msg: "DataView requires both tpl and itemSelector configurations to be defined."
87710             });
87711         }
87712
87713         me.callParent();
87714         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
87715             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
87716         }
87717
87718         // backwards compat alias for overClass/selectedClass
87719         // TODO: Consider support for overCls generation Ext.Component config
87720         if (isDef(me.overCls) || isDef(me.overClass)) {
87721             if (Ext.isDefined(Ext.global.console)) {
87722                 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
87723             }
87724             me.overItemCls = me.overCls || me.overClass;
87725             delete me.overCls;
87726             delete me.overClass;
87727         }
87728
87729         if (me.overItemCls) {
87730             me.trackOver = true;
87731         }
87732
87733         if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
87734             if (Ext.isDefined(Ext.global.console)) {
87735                 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
87736             }
87737             me.selectedItemCls = me.selectedCls || me.selectedClass;
87738             delete me.selectedCls;
87739             delete me.selectedClass;
87740         }
87741
87742         me.addEvents(
87743             /**
87744              * @event beforerefresh
87745              * Fires before the view is refreshed
87746              * @param {Ext.view.View} this The DataView object
87747              */
87748             'beforerefresh',
87749             /**
87750              * @event refresh
87751              * Fires when the view is refreshed
87752              * @param {Ext.view.View} this The DataView object
87753              */
87754             'refresh',
87755             /**
87756              * @event viewready
87757              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
87758              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
87759              * for selection until this event fires.
87760              * @param {Ext.view.View} this
87761              */
87762             'viewready',
87763             /**
87764              * @event itemupdate
87765              * Fires when the node associated with an individual record is updated
87766              * @param {Ext.data.Model} record The model instance
87767              * @param {Number} index The index of the record/node
87768              * @param {HTMLElement} node The node that has just been updated
87769              */
87770             'itemupdate',
87771             /**
87772              * @event itemadd
87773              * Fires when the nodes associated with an recordset have been added to the underlying store
87774              * @param {Ext.data.Model[]} records The model instance
87775              * @param {Number} index The index at which the set of record/nodes starts
87776              * @param {HTMLElement[]} node The node that has just been updated
87777              */
87778             'itemadd',
87779             /**
87780              * @event itemremove
87781              * Fires when the node associated with an individual record is removed
87782              * @param {Ext.data.Model} record The model instance
87783              * @param {Number} index The index of the record/node
87784              */
87785             'itemremove'
87786         );
87787
87788         me.addCmpEvents();
87789
87790         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
87791         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
87792         me.all = new Ext.CompositeElementLite();
87793     },
87794
87795     onRender: function() {
87796         var me = this,
87797             mask = me.loadMask,
87798             cfg = {
87799                 msg: me.loadingText,
87800                 msgCls: me.loadingCls,
87801                 useMsg: me.loadingUseMsg
87802             };
87803
87804         me.callParent(arguments);
87805
87806         if (mask) {
87807             // either a config object
87808             if (Ext.isObject(mask)) {
87809                 cfg = Ext.apply(cfg, mask);
87810             }
87811             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
87812             // If this DataView is floating, then mask this DataView.
87813             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
87814             // LoadMask captures the element upon render.
87815             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
87816             me.loadMask.on({
87817                 scope: me,
87818                 beforeshow: me.onMaskBeforeShow,
87819                 hide: me.onMaskHide
87820             });
87821         }
87822     },
87823
87824     onMaskBeforeShow: function(){
87825         var loadingHeight = this.loadingHeight;
87826         
87827         this.getSelectionModel().deselectAll();
87828         if (loadingHeight) {
87829             this.setCalculatedSize(undefined, loadingHeight);
87830         }
87831     },
87832
87833     onMaskHide: function(){
87834         var me = this;
87835         
87836         if (!me.destroying && me.loadingHeight) {
87837             me.setHeight(me.height);
87838         }
87839     },
87840
87841     afterRender: function() {
87842         this.callParent(arguments);
87843
87844         // Init the SelectionModel after any on('render') listeners have been added.
87845         // Drag plugins create a DragDrop instance in a render listener, and that needs
87846         // to see an itemmousedown event first.
87847         this.getSelectionModel().bindComponent(this);
87848     },
87849
87850     /**
87851      * Gets the selection model for this view.
87852      * @return {Ext.selection.Model} The selection model
87853      */
87854     getSelectionModel: function(){
87855         var me = this,
87856             mode = 'SINGLE';
87857
87858         if (!me.selModel) {
87859             me.selModel = {};
87860         }
87861
87862         if (me.simpleSelect) {
87863             mode = 'SIMPLE';
87864         } else if (me.multiSelect) {
87865             mode = 'MULTI';
87866         }
87867
87868         Ext.applyIf(me.selModel, {
87869             allowDeselect: me.allowDeselect,
87870             mode: mode
87871         });
87872
87873         if (!me.selModel.events) {
87874             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
87875         }
87876
87877         if (!me.selModel.hasRelaySetup) {
87878             me.relayEvents(me.selModel, [
87879                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
87880             ]);
87881             me.selModel.hasRelaySetup = true;
87882         }
87883
87884         // lock the selection model if user
87885         // has disabled selection
87886         if (me.disableSelection) {
87887             me.selModel.locked = true;
87888         }
87889
87890         return me.selModel;
87891     },
87892
87893     /**
87894      * Refreshes the view by reloading the data from the store and re-rendering the template.
87895      */
87896     refresh: function() {
87897         var me = this,
87898             el,
87899             records;
87900
87901         if (!me.rendered || me.isDestroyed) {
87902             return;
87903         }
87904
87905         me.fireEvent('beforerefresh', me);
87906         el = me.getTargetEl();
87907         records = me.store.getRange();
87908
87909         el.update('');
87910         if (records.length < 1) {
87911             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
87912                 el.update(me.emptyText);
87913             }
87914             me.all.clear();
87915         } else {
87916             me.tpl.overwrite(el, me.collectData(records, 0));
87917             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
87918             me.updateIndexes(0);
87919         }
87920
87921         me.selModel.refresh();
87922         me.hasSkippedEmptyText = true;
87923         me.fireEvent('refresh', me);
87924
87925         // Upon first refresh, fire the viewready event.
87926         // Reconfiguring the grid "renews" this event.
87927         if (!me.viewReady) {
87928             // Fire an event when deferred content becomes available.
87929             // This supports grid Panel's deferRowRender capability
87930             me.viewReady = true;
87931             me.fireEvent('viewready', me);
87932         }
87933     },
87934
87935     /**
87936      * Function which can be overridden to provide custom formatting for each Record that is used by this
87937      * DataView's {@link #tpl template} to render each node.
87938      * @param {Object/Object[]} data The raw data object that was used to create the Record.
87939      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
87940      * @param {Ext.data.Model} record The Record being prepared for rendering.
87941      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
87942      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
87943      */
87944     prepareData: function(data, index, record) {
87945         if (record) {
87946             Ext.apply(data, record.getAssociatedData());
87947         }
87948         return data;
87949     },
87950
87951     /**
87952      * <p>Function which can be overridden which returns the data object passed to this
87953      * DataView's {@link #tpl template} to render the whole DataView.</p>
87954      * <p>This is usually an Array of data objects, each element of which is processed by an
87955      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
87956      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
87957      * provide non-repeating data such as headings, totals etc.</p>
87958      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
87959      * @param {Number} startIndex the index number of the Record being prepared for rendering.
87960      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
87961      * contain <i>named</i> properties.
87962      */
87963     collectData : function(records, startIndex){
87964         var r = [],
87965             i = 0,
87966             len = records.length,
87967             record;
87968
87969         for(; i < len; i++){
87970             record = records[i];
87971             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
87972         }
87973         return r;
87974     },
87975
87976     // private
87977     bufferRender : function(records, index){
87978         var div = document.createElement('div');
87979         this.tpl.overwrite(div, this.collectData(records, index));
87980         return Ext.query(this.getItemSelector(), div);
87981     },
87982
87983     // private
87984     onUpdate : function(ds, record){
87985         var me = this,
87986             index = me.store.indexOf(record),
87987             node;
87988
87989         if (index > -1){
87990             node = me.bufferRender([record], index)[0];
87991             // ensure the node actually exists in the DOM
87992             if (me.getNode(record)) {
87993                 me.all.replaceElement(index, node, true);
87994                 me.updateIndexes(index, index);
87995                 // Maintain selection after update
87996                 // TODO: Move to approriate event handler.
87997                 me.selModel.refresh();
87998                 me.fireEvent('itemupdate', record, index, node);
87999             }
88000         }
88001
88002     },
88003
88004     // private
88005     onAdd : function(ds, records, index) {
88006         var me = this,
88007             nodes;
88008
88009         if (me.all.getCount() === 0) {
88010             me.refresh();
88011             return;
88012         }
88013
88014         nodes = me.bufferRender(records, index);
88015         me.doAdd(nodes, records, index);
88016
88017         me.selModel.refresh();
88018         me.updateIndexes(index);
88019         me.fireEvent('itemadd', records, index, nodes);
88020     },
88021
88022     doAdd: function(nodes, records, index) {
88023         var all = this.all;
88024
88025         if (index < all.getCount()) {
88026             all.item(index).insertSibling(nodes, 'before', true);
88027         } else {
88028             all.last().insertSibling(nodes, 'after', true);
88029         }
88030
88031         Ext.Array.insert(all.elements, index, nodes);
88032     },
88033
88034     // private
88035     onRemove : function(ds, record, index) {
88036         var me = this;
88037
88038         me.doRemove(record, index);
88039         me.updateIndexes(index);
88040         if (me.store.getCount() === 0){
88041             me.refresh();
88042         }
88043         me.fireEvent('itemremove', record, index);
88044     },
88045
88046     doRemove: function(record, index) {
88047         this.all.removeElement(index, true);
88048     },
88049
88050     /**
88051      * Refreshes an individual node's data from the store.
88052      * @param {Number} index The item's data index in the store
88053      */
88054     refreshNode : function(index){
88055         this.onUpdate(this.store, this.store.getAt(index));
88056     },
88057
88058     // private
88059     updateIndexes : function(startIndex, endIndex) {
88060         var ns = this.all.elements,
88061             records = this.store.getRange(),
88062             i;
88063             
88064         startIndex = startIndex || 0;
88065         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
88066         for(i = startIndex; i <= endIndex; i++){
88067             ns[i].viewIndex = i;
88068             ns[i].viewRecordId = records[i].internalId;
88069             if (!ns[i].boundView) {
88070                 ns[i].boundView = this.id;
88071             }
88072         }
88073     },
88074
88075     /**
88076      * Returns the store associated with this DataView.
88077      * @return {Ext.data.Store} The store
88078      */
88079     getStore : function(){
88080         return this.store;
88081     },
88082
88083     /**
88084      * Changes the data store bound to this view and refreshes it.
88085      * @param {Ext.data.Store} store The store to bind to this view
88086      */
88087     bindStore : function(store, initial) {
88088         var me = this,
88089             maskStore;
88090
88091         if (!initial && me.store) {
88092             if (store !== me.store && me.store.autoDestroy) {
88093                 me.store.destroyStore();
88094             }
88095             else {
88096                 me.mun(me.store, {
88097                     scope: me,
88098                     datachanged: me.onDataChanged,
88099                     add: me.onAdd,
88100                     remove: me.onRemove,
88101                     update: me.onUpdate,
88102                     clear: me.refresh
88103                 });
88104             }
88105             if (!store) {
88106                 // Ensure we have an instantiated LoadMask before we unbind it.
88107                 if (me.loadMask && me.loadMask.bindStore) {
88108                     me.loadMask.bindStore(null);
88109                 }
88110                 me.store = null;
88111             }
88112         }
88113         if (store) {
88114             store = Ext.data.StoreManager.lookup(store);
88115             me.mon(store, {
88116                 scope: me,
88117                 datachanged: me.onDataChanged,
88118                 add: me.onAdd,
88119                 remove: me.onRemove,
88120                 update: me.onUpdate,
88121                 clear: me.refresh
88122             });
88123             // Ensure we have an instantiated LoadMask before we bind it.
88124             if (me.loadMask && me.loadMask.bindStore) {
88125                 // View's store is a NodeStore, use owning TreePanel's Store
88126                 if (Ext.Array.contains(store.alias, 'store.node')) {
88127                     maskStore = this.ownerCt.store;
88128                 } else {
88129                     maskStore = store;
88130                 }
88131                 me.loadMask.bindStore(maskStore);
88132             }
88133         }
88134
88135         // Flag to say that initial refresh has not been performed.
88136         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
88137         me.viewReady = false;
88138
88139         me.store = store;
88140         // Bind the store to our selection model
88141         me.getSelectionModel().bind(store);
88142
88143         /*
88144          * This code used to have checks for:
88145          * if (store && (!initial || store.getCount() || me.emptyText)) {
88146          * Instead, just trigger a refresh and let the view itself figure out
88147          * what needs to happen. It can cause incorrect display if our store
88148          * has no data.
88149          */
88150         if (store) {
88151             if (initial && me.deferInitialRefresh) {
88152                 Ext.Function.defer(function () {
88153                     if (!me.isDestroyed) {
88154                         me.refresh(true);
88155                     }
88156                 }, 1);
88157             } else {
88158                 me.refresh(true);
88159             }
88160         }
88161     },
88162
88163     /**
88164      * @private
88165      * Calls this.refresh if this.blockRefresh is not true
88166      */
88167     onDataChanged: function() {
88168         if (this.blockRefresh !== true) {
88169             this.refresh.apply(this, arguments);
88170         }
88171     },
88172
88173     /**
88174      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
88175      * @param {HTMLElement} node
88176      * @return {HTMLElement} The template node
88177      */
88178     findItemByChild: function(node){
88179         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
88180     },
88181
88182     /**
88183      * Returns the template node by the Ext.EventObject or null if it is not found.
88184      * @param {Ext.EventObject} e
88185      */
88186     findTargetByEvent: function(e) {
88187         return e.getTarget(this.getItemSelector(), this.getTargetEl());
88188     },
88189
88190
88191     /**
88192      * Gets the currently selected nodes.
88193      * @return {HTMLElement[]} An array of HTMLElements
88194      */
88195     getSelectedNodes: function(){
88196         var nodes   = [],
88197             records = this.selModel.getSelection(),
88198             ln = records.length,
88199             i  = 0;
88200
88201         for (; i < ln; i++) {
88202             nodes.push(this.getNode(records[i]));
88203         }
88204
88205         return nodes;
88206     },
88207
88208     /**
88209      * Gets an array of the records from an array of nodes
88210      * @param {HTMLElement[]} nodes The nodes to evaluate
88211      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
88212      */
88213     getRecords: function(nodes) {
88214         var records = [],
88215             i = 0,
88216             len = nodes.length,
88217             data = this.store.data;
88218
88219         for (; i < len; i++) {
88220             records[records.length] = data.getByKey(nodes[i].viewRecordId);
88221         }
88222
88223         return records;
88224     },
88225
88226     /**
88227      * Gets a record from a node
88228      * @param {Ext.Element/HTMLElement} node The node to evaluate
88229      *
88230      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
88231      */
88232     getRecord: function(node){
88233         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
88234     },
88235
88236
88237     /**
88238      * Returns true if the passed node is selected, else false.
88239      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
88240      * @return {Boolean} True if selected, else false
88241      */
88242     isSelected : function(node) {
88243         // TODO: El/Idx/Record
88244         var r = this.getRecord(node);
88245         return this.selModel.isSelected(r);
88246     },
88247
88248     /**
88249      * Selects a record instance by record instance or index.
88250      * @param {Ext.data.Model[]/Number} records An array of records or an index
88251      * @param {Boolean} [keepExisting] True to keep existing selections
88252      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
88253      */
88254     select: function(records, keepExisting, suppressEvent) {
88255         this.selModel.select(records, keepExisting, suppressEvent);
88256     },
88257
88258     /**
88259      * Deselects a record instance by record instance or index.
88260      * @param {Ext.data.Model[]/Number} records An array of records or an index
88261      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
88262      */
88263     deselect: function(records, suppressEvent) {
88264         this.selModel.deselect(records, suppressEvent);
88265     },
88266
88267     /**
88268      * Gets a template node.
88269      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
88270      * the id of a template node or the record associated with the node.
88271      * @return {HTMLElement} The node or null if it wasn't found
88272      */
88273     getNode : function(nodeInfo) {
88274         if (!this.rendered) {
88275             return null;
88276         }
88277         if (Ext.isString(nodeInfo)) {
88278             return document.getElementById(nodeInfo);
88279         }
88280         if (Ext.isNumber(nodeInfo)) {
88281             return this.all.elements[nodeInfo];
88282         }
88283         if (nodeInfo instanceof Ext.data.Model) {
88284             return this.getNodeByRecord(nodeInfo);
88285         }
88286         return nodeInfo; // already an HTMLElement
88287     },
88288
88289     /**
88290      * @private
88291      */
88292     getNodeByRecord: function(record) {
88293         var ns = this.all.elements,
88294             ln = ns.length,
88295             i = 0;
88296
88297         for (; i < ln; i++) {
88298             if (ns[i].viewRecordId === record.internalId) {
88299                 return ns[i];
88300             }
88301         }
88302
88303         return null;
88304     },
88305
88306     /**
88307      * Gets a range nodes.
88308      * @param {Number} start (optional) The index of the first node in the range
88309      * @param {Number} end (optional) The index of the last node in the range
88310      * @return {HTMLElement[]} An array of nodes
88311      */
88312     getNodes: function(start, end) {
88313         var ns = this.all.elements,
88314             nodes = [],
88315             i;
88316
88317         start = start || 0;
88318         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
88319         if (start <= end) {
88320             for (i = start; i <= end && ns[i]; i++) {
88321                 nodes.push(ns[i]);
88322             }
88323         } else {
88324             for (i = start; i >= end && ns[i]; i--) {
88325                 nodes.push(ns[i]);
88326             }
88327         }
88328         return nodes;
88329     },
88330
88331     /**
88332      * Finds the index of the passed node.
88333      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
88334      * or a record associated with a node.
88335      * @return {Number} The index of the node or -1
88336      */
88337     indexOf: function(node) {
88338         node = this.getNode(node);
88339         if (Ext.isNumber(node.viewIndex)) {
88340             return node.viewIndex;
88341         }
88342         return this.all.indexOf(node);
88343     },
88344
88345     onDestroy : function() {
88346         var me = this;
88347
88348         me.all.clear();
88349         me.callParent();
88350         me.bindStore(null);
88351         me.selModel.destroy();
88352     },
88353
88354     // invoked by the selection model to maintain visual UI cues
88355     onItemSelect: function(record) {
88356         var node = this.getNode(record);
88357         
88358         if (node) {
88359             Ext.fly(node).addCls(this.selectedItemCls);
88360         }
88361     },
88362
88363     // invoked by the selection model to maintain visual UI cues
88364     onItemDeselect: function(record) {
88365         var node = this.getNode(record);
88366         
88367         if (node) {
88368             Ext.fly(node).removeCls(this.selectedItemCls);
88369         }
88370     },
88371
88372     getItemSelector: function() {
88373         return this.itemSelector;
88374     }
88375 }, function() {
88376     // all of this information is available directly
88377     // from the SelectionModel itself, the only added methods
88378     // to DataView regarding selection will perform some transformation/lookup
88379     // between HTMLElement/Nodes to records and vice versa.
88380     Ext.deprecate('extjs', '4.0', function() {
88381         Ext.view.AbstractView.override({
88382             /**
88383              * @cfg {Boolean} [multiSelect=false]
88384              * True to allow selection of more than one item at a time, false to allow selection of only a single item
88385              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
88386              */
88387             /**
88388              * @cfg {Boolean} [singleSelect=false]
88389              * True to allow selection of exactly one item at a time, false to allow no selection at all.
88390              * Note that if {@link #multiSelect} = true, this value will be ignored.
88391              */
88392             /**
88393              * @cfg {Boolean} [simpleSelect=false]
88394              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
88395              * false to force the user to hold Ctrl or Shift to select more than on item.
88396              */
88397
88398             /**
88399              * Gets the number of selected nodes.
88400              * @return {Number} The node count
88401              */
88402             getSelectionCount : function(){
88403                 if (Ext.global.console) {
88404                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
88405                 }
88406                 return this.selModel.getSelection().length;
88407             },
88408
88409             /**
88410              * Gets an array of the selected records
88411              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
88412              */
88413             getSelectedRecords : function(){
88414                 if (Ext.global.console) {
88415                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
88416                 }
88417                 return this.selModel.getSelection();
88418             },
88419
88420             select: function(records, keepExisting, supressEvents) {
88421                 if (Ext.global.console) {
88422                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
88423                 }
88424                 var sm = this.getSelectionModel();
88425                 return sm.select.apply(sm, arguments);
88426             },
88427
88428             clearSelections: function() {
88429                 if (Ext.global.console) {
88430                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
88431                 }
88432                 var sm = this.getSelectionModel();
88433                 return sm.deselectAll();
88434             }
88435         });
88436     });
88437 });
88438
88439 /**
88440  * @class Ext.Action
88441  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
88442  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
88443  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
88444  * and {@link Ext.menu.Menu} components).</p>
88445  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
88446  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
88447  * called at once through a single call to the Action.</p>
88448  * <p>Any Component that is to be configured with an Action must also support
88449  * the following methods:<ul>
88450  * <li><code>setText(string)</code></li>
88451  * <li><code>setIconCls(string)</code></li>
88452  * <li><code>setDisabled(boolean)</code></li>
88453  * <li><code>setVisible(boolean)</code></li>
88454  * <li><code>setHandler(function)</code></li></ul></p>
88455  * <p>This allows the Action to control its associated Components.</p>
88456  * Example usage:<br>
88457  * <pre><code>
88458 // Define the shared Action.  Each Component below will have the same
88459 // display text and icon, and will display the same message on click.
88460 var action = new Ext.Action({
88461     {@link #text}: 'Do something',
88462     {@link #handler}: function(){
88463         Ext.Msg.alert('Click', 'You did something.');
88464     },
88465     {@link #iconCls}: 'do-something',
88466     {@link #itemId}: 'myAction'
88467 });
88468
88469 var panel = new Ext.panel.Panel({
88470     title: 'Actions',
88471     width: 500,
88472     height: 300,
88473     tbar: [
88474         // Add the Action directly to a toolbar as a menu button
88475         action,
88476         {
88477             text: 'Action Menu',
88478             // Add the Action to a menu as a text item
88479             menu: [action]
88480         }
88481     ],
88482     items: [
88483         // Add the Action to the panel body as a standard button
88484         new Ext.button.Button(action)
88485     ],
88486     renderTo: Ext.getBody()
88487 });
88488
88489 // Change the text for all components using the Action
88490 action.setText('Something else');
88491
88492 // Reference an Action through a container using the itemId
88493 var btn = panel.getComponent('myAction');
88494 var aRef = btn.baseAction;
88495 aRef.setText('New text');
88496 </code></pre>
88497  */
88498 Ext.define('Ext.Action', {
88499
88500     /* Begin Definitions */
88501
88502     /* End Definitions */
88503
88504     /**
88505      * @cfg {String} [text='']
88506      * The text to set for all components configured by this Action.
88507      */
88508     /**
88509      * @cfg {String} [iconCls='']
88510      * The CSS class selector that specifies a background image to be used as the header icon for
88511      * all components configured by this Action.
88512      * <p>An example of specifying a custom icon class would be something like:
88513      * </p><pre><code>
88514 // specify the property in the config for the class:
88515      ...
88516      iconCls: 'do-something'
88517
88518 // css class that specifies background image to be used as the icon image:
88519 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
88520 </code></pre>
88521      */
88522     /**
88523      * @cfg {Boolean} [disabled=false]
88524      * True to disable all components configured by this Action, false to enable them.
88525      */
88526     /**
88527      * @cfg {Boolean} [hidden=false]
88528      * True to hide all components configured by this Action, false to show them.
88529      */
88530     /**
88531      * @cfg {Function} handler
88532      * The function that will be invoked by each component tied to this Action
88533      * when the component's primary event is triggered.
88534      */
88535     /**
88536      * @cfg {String} itemId
88537      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
88538      */
88539     /**
88540      * @cfg {Object} scope
88541      * The scope (this reference) in which the {@link #handler} is executed.
88542      * Defaults to the browser window.
88543      */
88544
88545     /**
88546      * Creates new Action.
88547      * @param {Object} config Config object.
88548      */
88549     constructor : function(config){
88550         this.initialConfig = config;
88551         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
88552         this.items = [];
88553     },
88554
88555     // private
88556     isAction : true,
88557
88558     /**
88559      * Sets the text to be displayed by all components configured by this Action.
88560      * @param {String} text The text to display
88561      */
88562     setText : function(text){
88563         this.initialConfig.text = text;
88564         this.callEach('setText', [text]);
88565     },
88566
88567     /**
88568      * Gets the text currently displayed by all components configured by this Action.
88569      */
88570     getText : function(){
88571         return this.initialConfig.text;
88572     },
88573
88574     /**
88575      * Sets the icon CSS class for all components configured by this Action.  The class should supply
88576      * a background image that will be used as the icon image.
88577      * @param {String} cls The CSS class supplying the icon image
88578      */
88579     setIconCls : function(cls){
88580         this.initialConfig.iconCls = cls;
88581         this.callEach('setIconCls', [cls]);
88582     },
88583
88584     /**
88585      * Gets the icon CSS class currently used by all components configured by this Action.
88586      */
88587     getIconCls : function(){
88588         return this.initialConfig.iconCls;
88589     },
88590
88591     /**
88592      * Sets the disabled state of all components configured by this Action.  Shortcut method
88593      * for {@link #enable} and {@link #disable}.
88594      * @param {Boolean} disabled True to disable the component, false to enable it
88595      */
88596     setDisabled : function(v){
88597         this.initialConfig.disabled = v;
88598         this.callEach('setDisabled', [v]);
88599     },
88600
88601     /**
88602      * Enables all components configured by this Action.
88603      */
88604     enable : function(){
88605         this.setDisabled(false);
88606     },
88607
88608     /**
88609      * Disables all components configured by this Action.
88610      */
88611     disable : function(){
88612         this.setDisabled(true);
88613     },
88614
88615     /**
88616      * Returns true if the components using this Action are currently disabled, else returns false.
88617      */
88618     isDisabled : function(){
88619         return this.initialConfig.disabled;
88620     },
88621
88622     /**
88623      * Sets the hidden state of all components configured by this Action.  Shortcut method
88624      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
88625      * @param {Boolean} hidden True to hide the component, false to show it
88626      */
88627     setHidden : function(v){
88628         this.initialConfig.hidden = v;
88629         this.callEach('setVisible', [!v]);
88630     },
88631
88632     /**
88633      * Shows all components configured by this Action.
88634      */
88635     show : function(){
88636         this.setHidden(false);
88637     },
88638
88639     /**
88640      * Hides all components configured by this Action.
88641      */
88642     hide : function(){
88643         this.setHidden(true);
88644     },
88645
88646     /**
88647      * Returns true if the components configured by this Action are currently hidden, else returns false.
88648      */
88649     isHidden : function(){
88650         return this.initialConfig.hidden;
88651     },
88652
88653     /**
88654      * Sets the function that will be called by each Component using this action when its primary event is triggered.
88655      * @param {Function} fn The function that will be invoked by the action's components.  The function
88656      * will be called with no arguments.
88657      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
88658      */
88659     setHandler : function(fn, scope){
88660         this.initialConfig.handler = fn;
88661         this.initialConfig.scope = scope;
88662         this.callEach('setHandler', [fn, scope]);
88663     },
88664
88665     /**
88666      * Executes the specified function once for each Component currently tied to this Action.  The function passed
88667      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
88668      * @param {Function} fn The function to execute for each component
88669      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
88670      */
88671     each : function(fn, scope){
88672         Ext.each(this.items, fn, scope);
88673     },
88674
88675     // private
88676     callEach : function(fnName, args){
88677         var items = this.items,
88678             i = 0,
88679             len = items.length;
88680
88681         for(; i < len; i++){
88682             items[i][fnName].apply(items[i], args);
88683         }
88684     },
88685
88686     // private
88687     addComponent : function(comp){
88688         this.items.push(comp);
88689         comp.on('destroy', this.removeComponent, this);
88690     },
88691
88692     // private
88693     removeComponent : function(comp){
88694         Ext.Array.remove(this.items, comp);
88695     },
88696
88697     /**
88698      * Executes this Action manually using the handler function specified in the original config object
88699      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
88700      * function will be passed on to the handler function.
88701      * @param {Object...} args (optional) Variable number of arguments passed to the handler function
88702      */
88703     execute : function(){
88704         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
88705     }
88706 });
88707
88708 /**
88709  * Component layout for editors
88710  * @class Ext.layout.component.Editor
88711  * @extends Ext.layout.component.Component
88712  * @private
88713  */
88714 Ext.define('Ext.layout.component.Editor', {
88715
88716     /* Begin Definitions */
88717
88718     alias: ['layout.editor'],
88719
88720     extend: 'Ext.layout.component.Component',
88721
88722     /* End Definitions */
88723
88724     onLayout: function(width, height) {
88725         var me = this,
88726             owner = me.owner,
88727             autoSize = owner.autoSize;
88728             
88729         if (autoSize === true) {
88730             autoSize = {
88731                 width: 'field',
88732                 height: 'field'    
88733             };
88734         }
88735         
88736         if (autoSize) {
88737             width = me.getDimension(owner, autoSize.width, 'Width', width);
88738             height = me.getDimension(owner, autoSize.height, 'Height', height);
88739         }
88740         me.setTargetSize(width, height);
88741         owner.field.setSize(width, height);
88742     },
88743     
88744     getDimension: function(owner, type, dimension, actual){
88745         var method = 'get' + dimension;
88746         switch (type) {
88747             case 'boundEl':
88748                 return owner.boundEl[method]();
88749             case 'field':
88750                 return owner.field[method]();
88751             default:
88752                 return actual;
88753         }
88754     }
88755 });
88756 /**
88757  * @class Ext.Editor
88758  * @extends Ext.Component
88759  *
88760  * <p>
88761  * The Editor class is used to provide inline editing for elements on the page. The editor
88762  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
88763  * The editor is a floating Component, when the editor is shown it is automatically aligned to
88764  * display over the top of the bound element it is editing. The Editor contains several options
88765  * for how to handle key presses:
88766  * <ul>
88767  * <li>{@link #completeOnEnter}</li>
88768  * <li>{@link #cancelOnEsc}</li>
88769  * <li>{@link #swallowKeys}</li>
88770  * </ul>
88771  * It also has options for how to use the value once the editor has been activated:
88772  * <ul>
88773  * <li>{@link #revertInvalid}</li>
88774  * <li>{@link #ignoreNoChange}</li>
88775  * <li>{@link #updateEl}</li>
88776  * </ul>
88777  * Sample usage:
88778  * </p>
88779  * <pre><code>
88780 var editor = new Ext.Editor({
88781     updateEl: true, // update the innerHTML of the bound element when editing completes
88782     field: {
88783         xtype: 'textfield'
88784     }
88785 });
88786 var el = Ext.get('my-text'); // The element to 'edit'
88787 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
88788  * </code></pre>
88789  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
88790  *
88791  */
88792 Ext.define('Ext.Editor', {
88793
88794     /* Begin Definitions */
88795
88796     extend: 'Ext.Component',
88797
88798     alias: 'widget.editor',
88799
88800     requires: ['Ext.layout.component.Editor'],
88801
88802     /* End Definitions */
88803
88804    componentLayout: 'editor',
88805
88806     /**
88807     * @cfg {Ext.form.field.Field} field
88808     * The Field object (or descendant) or config object for field
88809     */
88810
88811     /**
88812      * @cfg {Boolean} allowBlur
88813      * True to {@link #completeEdit complete the editing process} if in edit mode when the
88814      * field is blurred.
88815      */
88816     allowBlur: true,
88817
88818     /**
88819      * @cfg {Boolean/Object} autoSize
88820      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
88821      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
88822      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
88823      * the editor object.
88824      * Examples:
88825      * <pre><code>
88826 autoSize: true // The editor will be sized to the height/width of the field
88827
88828 height: 21,
88829 autoSize: {
88830     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
88831 }
88832
88833 autoSize: {
88834     width: 'field', // Width from the field
88835     height: 'boundEl' // Height from the boundEl
88836 }
88837      * </pre></code>
88838      */
88839
88840     /**
88841      * @cfg {Boolean} revertInvalid
88842      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
88843      * validation fails
88844      */
88845     revertInvalid: true,
88846
88847     /**
88848      * @cfg {Boolean} [ignoreNoChange=false]
88849      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
88850      * the value has not changed.  Applies only to string values - edits for other data types
88851      * will never be ignored.
88852      */
88853
88854     /**
88855      * @cfg {Boolean} [hideEl=true]
88856      * False to keep the bound element visible while the editor is displayed
88857      */
88858
88859     /**
88860      * @cfg {Object} value
88861      * The data value of the underlying field
88862      */
88863     value : '',
88864
88865     /**
88866      * @cfg {String} alignment
88867      * The position to align to (see {@link Ext.Element#alignTo} for more details).
88868      */
88869     alignment: 'c-c?',
88870
88871     /**
88872      * @cfg {Number[]} offsets
88873      * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
88874      */
88875     offsets: [0, 0],
88876
88877     /**
88878      * @cfg {Boolean/String} shadow
88879      * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
88880      */
88881     shadow : 'frame',
88882
88883     /**
88884      * @cfg {Boolean} constrain
88885      * True to constrain the editor to the viewport
88886      */
88887     constrain : false,
88888
88889     /**
88890      * @cfg {Boolean} swallowKeys
88891      * Handle the keydown/keypress events so they don't propagate
88892      */
88893     swallowKeys : true,
88894
88895     /**
88896      * @cfg {Boolean} completeOnEnter
88897      * True to complete the edit when the enter key is pressed.
88898      */
88899     completeOnEnter : true,
88900
88901     /**
88902      * @cfg {Boolean} cancelOnEsc
88903      * True to cancel the edit when the escape key is pressed.
88904      */
88905     cancelOnEsc : true,
88906
88907     /**
88908      * @cfg {Boolean} updateEl
88909      * True to update the innerHTML of the bound element when the update completes
88910      */
88911     updateEl : false,
88912
88913     /**
88914      * @cfg {String/HTMLElement/Ext.Element} parentEl
88915      * An element to render to. Defaults to the <tt>document.body</tt>.
88916      */
88917
88918     // private overrides
88919     hidden: true,
88920     baseCls: Ext.baseCSSPrefix + 'editor',
88921
88922     initComponent : function() {
88923         var me = this,
88924             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
88925
88926         Ext.apply(field, {
88927             inEditor: true,
88928             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
88929         });
88930         me.mon(field, {
88931             scope: me,
88932             blur: {
88933                 fn: me.onBlur,
88934                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
88935                 delay: 1
88936             },
88937             specialkey: me.onSpecialKey
88938         });
88939
88940         if (field.grow) {
88941             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
88942         }
88943         me.floating = {
88944             constrain: me.constrain
88945         };
88946
88947         me.callParent(arguments);
88948
88949         me.addEvents(
88950             /**
88951              * @event beforestartedit
88952              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
88953              * false from the handler of this event.
88954              * @param {Ext.Editor} this
88955              * @param {Ext.Element} boundEl The underlying element bound to this editor
88956              * @param {Object} value The field value being set
88957              */
88958             'beforestartedit',
88959
88960             /**
88961              * @event startedit
88962              * Fires when this editor is displayed
88963              * @param {Ext.Editor} this
88964              * @param {Ext.Element} boundEl The underlying element bound to this editor
88965              * @param {Object} value The starting field value
88966              */
88967             'startedit',
88968
88969             /**
88970              * @event beforecomplete
88971              * Fires after a change has been made to the field, but before the change is reflected in the underlying
88972              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
88973              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
88974              * event will not fire since no edit actually occurred.
88975              * @param {Ext.Editor} this
88976              * @param {Object} value The current field value
88977              * @param {Object} startValue The original field value
88978              */
88979             'beforecomplete',
88980             /**
88981              * @event complete
88982              * Fires after editing is complete and any changed value has been written to the underlying field.
88983              * @param {Ext.Editor} this
88984              * @param {Object} value The current field value
88985              * @param {Object} startValue The original field value
88986              */
88987             'complete',
88988             /**
88989              * @event canceledit
88990              * Fires after editing has been canceled and the editor's value has been reset.
88991              * @param {Ext.Editor} this
88992              * @param {Object} value The user-entered field value that was discarded
88993              * @param {Object} startValue The original field value that was set back into the editor after cancel
88994              */
88995             'canceledit',
88996             /**
88997              * @event specialkey
88998              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
88999              * {@link Ext.EventObject#getKey} to determine which key was pressed.
89000              * @param {Ext.Editor} this
89001              * @param {Ext.form.field.Field} The field attached to this editor
89002              * @param {Ext.EventObject} event The event object
89003              */
89004             'specialkey'
89005         );
89006     },
89007
89008     // private
89009     onAutoSize: function(){
89010         this.doComponentLayout();
89011     },
89012
89013     // private
89014     onRender : function(ct, position) {
89015         var me = this,
89016             field = me.field,
89017             inputEl = field.inputEl;
89018
89019         me.callParent(arguments);
89020
89021         field.render(me.el);
89022         //field.hide();
89023         // Ensure the field doesn't get submitted as part of any form
89024         if (inputEl) {
89025             inputEl.dom.name = '';
89026             if (me.swallowKeys) {
89027                 inputEl.swallowEvent([
89028                     'keypress', // *** Opera
89029                     'keydown'   // *** all other browsers
89030                 ]);
89031             }
89032         }
89033     },
89034
89035     // private
89036     onSpecialKey : function(field, event) {
89037         var me = this,
89038             key = event.getKey(),
89039             complete = me.completeOnEnter && key == event.ENTER,
89040             cancel = me.cancelOnEsc && key == event.ESC;
89041
89042         if (complete || cancel) {
89043             event.stopEvent();
89044             // Must defer this slightly to prevent exiting edit mode before the field's own
89045             // key nav can handle the enter key, e.g. selecting an item in a combobox list
89046             Ext.defer(function() {
89047                 if (complete) {
89048                     me.completeEdit();
89049                 } else {
89050                     me.cancelEdit();
89051                 }
89052                 if (field.triggerBlur) {
89053                     field.triggerBlur();
89054                 }
89055             }, 10);
89056         }
89057
89058         this.fireEvent('specialkey', this, field, event);
89059     },
89060
89061     /**
89062      * Starts the editing process and shows the editor.
89063      * @param {String/HTMLElement/Ext.Element} el The element to edit
89064      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
89065       * to the innerHTML of el.
89066      */
89067     startEdit : function(el, value) {
89068         var me = this,
89069             field = me.field;
89070
89071         me.completeEdit();
89072         me.boundEl = Ext.get(el);
89073         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
89074
89075         if (!me.rendered) {
89076             me.render(me.parentEl || document.body);
89077         }
89078
89079         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
89080             me.startValue = value;
89081             me.show();
89082             field.reset();
89083             field.setValue(value);
89084             me.realign(true);
89085             field.focus(false, 10);
89086             if (field.autoSize) {
89087                 field.autoSize();
89088             }
89089             me.editing = true;
89090         }
89091     },
89092
89093     /**
89094      * Realigns the editor to the bound field based on the current alignment config value.
89095      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
89096      */
89097     realign : function(autoSize) {
89098         var me = this;
89099         if (autoSize === true) {
89100             me.doComponentLayout();
89101         }
89102         me.alignTo(me.boundEl, me.alignment, me.offsets);
89103     },
89104
89105     /**
89106      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
89107      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
89108      */
89109     completeEdit : function(remainVisible) {
89110         var me = this,
89111             field = me.field,
89112             value;
89113
89114         if (!me.editing) {
89115             return;
89116         }
89117
89118         // Assert combo values first
89119         if (field.assertValue) {
89120             field.assertValue();
89121         }
89122
89123         value = me.getValue();
89124         if (!field.isValid()) {
89125             if (me.revertInvalid !== false) {
89126                 me.cancelEdit(remainVisible);
89127             }
89128             return;
89129         }
89130
89131         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
89132             me.hideEdit(remainVisible);
89133             return;
89134         }
89135
89136         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
89137             // Grab the value again, may have changed in beforecomplete
89138             value = me.getValue();
89139             if (me.updateEl && me.boundEl) {
89140                 me.boundEl.update(value);
89141             }
89142             me.hideEdit(remainVisible);
89143             me.fireEvent('complete', me, value, me.startValue);
89144         }
89145     },
89146
89147     // private
89148     onShow : function() {
89149         var me = this;
89150
89151         me.callParent(arguments);
89152         if (me.hideEl !== false) {
89153             me.boundEl.hide();
89154         }
89155         me.fireEvent("startedit", me.boundEl, me.startValue);
89156     },
89157
89158     /**
89159      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
89160      * reverted to the original starting value.
89161      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
89162      */
89163     cancelEdit : function(remainVisible) {
89164         var me = this,
89165             startValue = me.startValue,
89166             value;
89167
89168         if (me.editing) {
89169             value = me.getValue();
89170             me.setValue(startValue);
89171             me.hideEdit(remainVisible);
89172             me.fireEvent('canceledit', me, value, startValue);
89173         }
89174     },
89175
89176     // private
89177     hideEdit: function(remainVisible) {
89178         if (remainVisible !== true) {
89179             this.editing = false;
89180             this.hide();
89181         }
89182     },
89183
89184     // private
89185     onBlur : function() {
89186         var me = this;
89187
89188         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
89189         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
89190             me.completeEdit();
89191         }
89192     },
89193
89194     // private
89195     onHide : function() {
89196         var me = this,
89197             field = me.field;
89198
89199         if (me.editing) {
89200             me.completeEdit();
89201             return;
89202         }
89203         field.blur();
89204         if (field.collapse) {
89205             field.collapse();
89206         }
89207
89208         //field.hide();
89209         if (me.hideEl !== false) {
89210             me.boundEl.show();
89211         }
89212         me.callParent(arguments);
89213     },
89214
89215     /**
89216      * Sets the data value of the editor
89217      * @param {Object} value Any valid value supported by the underlying field
89218      */
89219     setValue : function(value) {
89220         this.field.setValue(value);
89221     },
89222
89223     /**
89224      * Gets the data value of the editor
89225      * @return {Object} The data value
89226      */
89227     getValue : function() {
89228         return this.field.getValue();
89229     },
89230
89231     beforeDestroy : function() {
89232         var me = this;
89233
89234         Ext.destroy(me.field);
89235         delete me.field;
89236         delete me.parentEl;
89237         delete me.boundEl;
89238
89239         me.callParent(arguments);
89240     }
89241 });
89242 /**
89243  * @class Ext.Img
89244  * @extends Ext.Component
89245  *
89246  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
89247  * with the configured src.
89248  *
89249  * {@img Ext.Img/Ext.Img.png Ext.Img component}
89250  *
89251  * ## Example usage: 
89252  *
89253  *     var changingImage = Ext.create('Ext.Img', {
89254  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
89255  *         renderTo: Ext.getBody()
89256  *     });
89257  *      
89258  *     // change the src of the image programmatically
89259  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
89260 */
89261 Ext.define('Ext.Img', {
89262     extend: 'Ext.Component',
89263     alias: ['widget.image', 'widget.imagecomponent'],
89264     /** @cfg {String} src The image src */
89265     src: '',
89266
89267     getElConfig: function() {
89268         return {
89269             tag: 'img',
89270             src: this.src
89271         };
89272     },
89273     
89274     // null out this function, we can't set any html inside the image
89275     initRenderTpl: Ext.emptyFn,
89276     
89277     /**
89278      * Updates the {@link #src} of the image
89279      */
89280     setSrc: function(src) {
89281         var me = this,
89282             img = me.el;
89283         me.src = src;
89284         if (img) {
89285             img.dom.src = src;
89286         }
89287     }
89288 });
89289
89290 /**
89291  * @class Ext.Layer
89292  * @extends Ext.Element
89293  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
89294  * automatic maintaining of shadow/shim positions.
89295  *
89296  * @cfg {Boolean} [shim=true]
89297  * False to disable the iframe shim in browsers which need one.
89298  *
89299  * @cfg {String/Boolean} [shadow=false]
89300  * True to automatically create an {@link Ext.Shadow}, or a string indicating the
89301  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
89302  *
89303  * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
89304  * DomHelper object config to create element with.
89305  *
89306  * @cfg {Boolean} [constrain=true]
89307  * False to disable constrain to viewport.
89308  *
89309  * @cfg {String} cls
89310  * CSS class to add to the element
89311  *
89312  * @cfg {Number} [zindex=11000]
89313  * Starting z-index.
89314  *
89315  * @cfg {Number} [shadowOffset=4]
89316  * Number of pixels to offset the shadow
89317  *
89318  * @cfg {Boolean} [useDisplay=false]
89319  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
89320  * to use css style <tt>'display:none;'</tt> to hide the Layer.
89321  *
89322  * @cfg {String} visibilityCls
89323  * The CSS class name to add in order to hide this Layer if this layer
89324  * is configured with <code>{@link #hideMode}: 'asclass'</code>
89325  *
89326  * @cfg {String} hideMode
89327  * A String which specifies how this Layer will be hidden.
89328  * Values may be<div class="mdetail-params"><ul>
89329  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
89330  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
89331  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
89332  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
89333  * in a Component having zero dimensions.</li></ul></div>
89334  */
89335 Ext.define('Ext.Layer', {
89336     uses: ['Ext.Shadow'],
89337
89338     // shims are shared among layer to keep from having 100 iframes
89339     statics: {
89340         shims: []
89341     },
89342
89343     extend: 'Ext.Element',
89344
89345     /**
89346      * Creates new Layer.
89347      * @param {Object} config (optional) An object with config options.
89348      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
89349      * If the element is not found it creates it.
89350      */
89351     constructor: function(config, existingEl) {
89352         config = config || {};
89353         var me = this,
89354             dh = Ext.DomHelper,
89355             cp = config.parentEl,
89356             pel = cp ? Ext.getDom(cp) : document.body,
89357         hm = config.hideMode;
89358
89359         if (existingEl) {
89360             me.dom = Ext.getDom(existingEl);
89361         }
89362         if (!me.dom) {
89363             me.dom = dh.append(pel, config.dh || {
89364                 tag: 'div',
89365                 cls: Ext.baseCSSPrefix + 'layer'
89366             });
89367         } else {
89368             me.addCls(Ext.baseCSSPrefix + 'layer');
89369             if (!me.dom.parentNode) {
89370                 pel.appendChild(me.dom);
89371             }
89372         }
89373
89374         if (config.cls) {
89375             me.addCls(config.cls);
89376         }
89377         me.constrain = config.constrain !== false;
89378
89379         // Allow Components to pass their hide mode down to the Layer if they are floating.
89380         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
89381         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
89382         if (hm) {
89383             me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
89384             if (me.visibilityMode == Ext.Element.ASCLASS) {
89385                 me.visibilityCls = config.visibilityCls;
89386             }
89387         } else if (config.useDisplay) {
89388             me.setVisibilityMode(Ext.Element.DISPLAY);
89389         } else {
89390             me.setVisibilityMode(Ext.Element.VISIBILITY);
89391         }
89392
89393         if (config.id) {
89394             me.id = me.dom.id = config.id;
89395         } else {
89396             me.id = Ext.id(me.dom);
89397         }
89398         me.position('absolute');
89399         if (config.shadow) {
89400             me.shadowOffset = config.shadowOffset || 4;
89401             me.shadow = Ext.create('Ext.Shadow', {
89402                 offset: me.shadowOffset,
89403                 mode: config.shadow
89404             });
89405             me.disableShadow();
89406         } else {
89407             me.shadowOffset = 0;
89408         }
89409         me.useShim = config.shim !== false && Ext.useShims;
89410         if (config.hidden === true) {
89411             me.hide();
89412         } else {
89413             me.show();
89414         }
89415     },
89416
89417     getZIndex: function() {
89418         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
89419     },
89420
89421     getShim: function() {
89422         var me = this,
89423             shim, pn;
89424
89425         if (!me.useShim) {
89426             return null;
89427         }
89428         if (!me.shim) {
89429             shim = me.self.shims.shift();
89430             if (!shim) {
89431                 shim = me.createShim();
89432                 shim.enableDisplayMode('block');
89433                 shim.hide();
89434             }
89435             pn = me.dom.parentNode;
89436             if (shim.dom.parentNode != pn) {
89437                 pn.insertBefore(shim.dom, me.dom);
89438             }
89439             me.shim = shim;
89440         }
89441         return me.shim;
89442     },
89443
89444     hideShim: function() {
89445         var me = this;
89446         
89447         if (me.shim) {
89448             me.shim.setDisplayed(false);
89449             me.self.shims.push(me.shim);
89450             delete me.shim;
89451         }
89452     },
89453
89454     disableShadow: function() {
89455         var me = this;
89456         
89457         if (me.shadow && !me.shadowDisabled) {
89458             me.shadowDisabled = true;
89459             me.shadow.hide();
89460             me.lastShadowOffset = me.shadowOffset;
89461             me.shadowOffset = 0;
89462         }
89463     },
89464
89465     enableShadow: function(show) {
89466         var me = this;
89467         
89468         if (me.shadow && me.shadowDisabled) {
89469             me.shadowDisabled = false;
89470             me.shadowOffset = me.lastShadowOffset;
89471             delete me.lastShadowOffset;
89472             if (show) {
89473                 me.sync(true);
89474             }
89475         }
89476     },
89477
89478     /**
89479      * @private
89480      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
89481      * <p>This code can execute repeatedly in milliseconds,
89482      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
89483      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
89484      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
89485      */
89486     sync: function(doShow) {
89487         var me = this,
89488             shadow = me.shadow,
89489             shadowPos, shimStyle, shadowSize;
89490
89491         if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
89492             var shim = me.getShim(),
89493                 l = me.getLeft(true),
89494                 t = me.getTop(true),
89495                 w = me.dom.offsetWidth,
89496                 h = me.dom.offsetHeight,
89497                 shimIndex;
89498
89499             if (shadow && !me.shadowDisabled) {
89500                 if (doShow && !shadow.isVisible()) {
89501                     shadow.show(me);
89502                 } else {
89503                     shadow.realign(l, t, w, h);
89504                 }
89505                 if (shim) {
89506                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
89507                     shimIndex = shim.getStyle('z-index');
89508                     if (shimIndex > me.zindex) {
89509                         me.shim.setStyle('z-index', me.zindex - 2);
89510                     }
89511                     shim.show();
89512                     // fit the shim behind the shadow, so it is shimmed too
89513                     if (shadow.isVisible()) {
89514                         shadowPos = shadow.el.getXY();
89515                         shimStyle = shim.dom.style;
89516                         shadowSize = shadow.el.getSize();
89517                         if (Ext.supports.CSS3BoxShadow) {
89518                             shadowSize.height += 6;
89519                             shadowSize.width += 4;
89520                             shadowPos[0] -= 2;
89521                             shadowPos[1] -= 4;
89522                         }
89523                         shimStyle.left = (shadowPos[0]) + 'px';
89524                         shimStyle.top = (shadowPos[1]) + 'px';
89525                         shimStyle.width = (shadowSize.width) + 'px';
89526                         shimStyle.height = (shadowSize.height) + 'px';
89527                     } else {
89528                         shim.setSize(w, h);
89529                         shim.setLeftTop(l, t);
89530                     }
89531                 }
89532             } else if (shim) {
89533                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
89534                 shimIndex = shim.getStyle('z-index');
89535                 if (shimIndex > me.zindex) {
89536                     me.shim.setStyle('z-index', me.zindex - 2);
89537                 }
89538                 shim.show();
89539                 shim.setSize(w, h);
89540                 shim.setLeftTop(l, t);
89541             }
89542         }
89543         return me;
89544     },
89545
89546     remove: function() {
89547         this.hideUnders();
89548         this.callParent();
89549     },
89550
89551     // private
89552     beginUpdate: function() {
89553         this.updating = true;
89554     },
89555
89556     // private
89557     endUpdate: function() {
89558         this.updating = false;
89559         this.sync(true);
89560     },
89561
89562     // private
89563     hideUnders: function() {
89564         if (this.shadow) {
89565             this.shadow.hide();
89566         }
89567         this.hideShim();
89568     },
89569
89570     // private
89571     constrainXY: function() {
89572         if (this.constrain) {
89573             var vw = Ext.Element.getViewWidth(),
89574                 vh = Ext.Element.getViewHeight(),
89575                 s = Ext.getDoc().getScroll(),
89576                 xy = this.getXY(),
89577                 x = xy[0],
89578                 y = xy[1],
89579                 so = this.shadowOffset,
89580                 w = this.dom.offsetWidth + so,
89581                 h = this.dom.offsetHeight + so,
89582                 moved = false; // only move it if it needs it
89583             // first validate right/bottom
89584             if ((x + w) > vw + s.left) {
89585                 x = vw - w - so;
89586                 moved = true;
89587             }
89588             if ((y + h) > vh + s.top) {
89589                 y = vh - h - so;
89590                 moved = true;
89591             }
89592             // then make sure top/left isn't negative
89593             if (x < s.left) {
89594                 x = s.left;
89595                 moved = true;
89596             }
89597             if (y < s.top) {
89598                 y = s.top;
89599                 moved = true;
89600             }
89601             if (moved) {
89602                 Ext.Layer.superclass.setXY.call(this, [x, y]);
89603                 this.sync();
89604             }
89605         }
89606         return this;
89607     },
89608
89609     getConstrainOffset: function() {
89610         return this.shadowOffset;
89611     },
89612
89613     // overridden Element method
89614     setVisible: function(visible, animate, duration, callback, easing) {
89615         var me = this,
89616             cb;
89617
89618         // post operation processing
89619         cb = function() {
89620             if (visible) {
89621                 me.sync(true);
89622             }
89623             if (callback) {
89624                 callback();
89625             }
89626         };
89627
89628         // Hide shadow and shim if hiding
89629         if (!visible) {
89630             me.hideUnders(true);
89631         }
89632         me.callParent([visible, animate, duration, callback, easing]);
89633         if (!animate) {
89634             cb();
89635         }
89636         return me;
89637     },
89638
89639     // private
89640     beforeFx: function() {
89641         this.beforeAction();
89642         return this.callParent(arguments);
89643     },
89644
89645     // private
89646     afterFx: function() {
89647         this.callParent(arguments);
89648         this.sync(this.isVisible());
89649     },
89650
89651     // private
89652     beforeAction: function() {
89653         if (!this.updating && this.shadow) {
89654             this.shadow.hide();
89655         }
89656     },
89657
89658     // overridden Element method
89659     setLeft: function(left) {
89660         this.callParent(arguments);
89661         return this.sync();
89662     },
89663
89664     setTop: function(top) {
89665         this.callParent(arguments);
89666         return this.sync();
89667     },
89668
89669     setLeftTop: function(left, top) {
89670         this.callParent(arguments);
89671         return this.sync();
89672     },
89673
89674     setXY: function(xy, animate, duration, callback, easing) {
89675         var me = this;
89676         
89677         // Callback will restore shadow state and call the passed callback
89678         callback = me.createCB(callback);
89679
89680         me.fixDisplay();
89681         me.beforeAction();
89682         me.callParent([xy, animate, duration, callback, easing]);
89683         if (!animate) {
89684             callback();
89685         }
89686         return me;
89687     },
89688
89689     // private
89690     createCB: function(callback) {
89691         var me = this,
89692             showShadow = me.shadow && me.shadow.isVisible();
89693
89694         return function() {
89695             me.constrainXY();
89696             me.sync(showShadow);
89697             if (callback) {
89698                 callback();
89699             }
89700         };
89701     },
89702
89703     // overridden Element method
89704     setX: function(x, animate, duration, callback, easing) {
89705         this.setXY([x, this.getY()], animate, duration, callback, easing);
89706         return this;
89707     },
89708
89709     // overridden Element method
89710     setY: function(y, animate, duration, callback, easing) {
89711         this.setXY([this.getX(), y], animate, duration, callback, easing);
89712         return this;
89713     },
89714
89715     // overridden Element method
89716     setSize: function(w, h, animate, duration, callback, easing) {
89717         var me = this;
89718         
89719         // Callback will restore shadow state and call the passed callback
89720         callback = me.createCB(callback);
89721
89722         me.beforeAction();
89723         me.callParent([w, h, animate, duration, callback, easing]);
89724         if (!animate) {
89725             callback();
89726         }
89727         return me;
89728     },
89729
89730     // overridden Element method
89731     setWidth: function(w, animate, duration, callback, easing) {
89732         var me = this;
89733         
89734         // Callback will restore shadow state and call the passed callback
89735         callback = me.createCB(callback);
89736
89737         me.beforeAction();
89738         me.callParent([w, animate, duration, callback, easing]);
89739         if (!animate) {
89740             callback();
89741         }
89742         return me;
89743     },
89744
89745     // overridden Element method
89746     setHeight: function(h, animate, duration, callback, easing) {
89747         var me = this;
89748         
89749         // Callback will restore shadow state and call the passed callback
89750         callback = me.createCB(callback);
89751
89752         me.beforeAction();
89753         me.callParent([h, animate, duration, callback, easing]);
89754         if (!animate) {
89755             callback();
89756         }
89757         return me;
89758     },
89759
89760     // overridden Element method
89761     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
89762         var me = this;
89763         
89764         // Callback will restore shadow state and call the passed callback
89765         callback = me.createCB(callback);
89766
89767         me.beforeAction();
89768         if (!animate) {
89769             Ext.Layer.superclass.setXY.call(me, [x, y]);
89770             Ext.Layer.superclass.setSize.call(me, width, height);
89771             callback();
89772         } else {
89773             me.callParent([x, y, width, height, animate, duration, callback, easing]);
89774         }
89775         return me;
89776     },
89777
89778     /**
89779      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
89780      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
89781      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
89782      * element will receive the highest  z-index.
89783      * @param {Number} zindex The new z-index to set
89784      * @return {Ext.Layer} The Layer
89785      */
89786     setZIndex: function(zindex) {
89787         var me = this;
89788         
89789         me.zindex = zindex;
89790         if (me.getShim()) {
89791             me.shim.setStyle('z-index', zindex++);
89792         }
89793         if (me.shadow) {
89794             me.shadow.setZIndex(zindex++);
89795         }
89796         return me.setStyle('z-index', zindex);
89797     },
89798     
89799     setOpacity: function(opacity){
89800         if (this.shadow) {
89801             this.shadow.setOpacity(opacity);
89802         }
89803         return this.callParent(arguments);
89804     }
89805 });
89806
89807 /**
89808  * @class Ext.layout.component.ProgressBar
89809  * @extends Ext.layout.component.Component
89810  * @private
89811  */
89812
89813 Ext.define('Ext.layout.component.ProgressBar', {
89814
89815     /* Begin Definitions */
89816
89817     alias: ['layout.progressbar'],
89818
89819     extend: 'Ext.layout.component.Component',
89820
89821     /* End Definitions */
89822
89823     type: 'progressbar',
89824
89825     onLayout: function(width, height) {
89826         var me = this,
89827             owner = me.owner,
89828             textEl = owner.textEl;
89829         
89830         me.setElementSize(owner.el, width, height);
89831         textEl.setWidth(owner.el.getWidth(true));
89832         
89833         me.callParent([width, height]);
89834         
89835         owner.updateProgress(owner.value);
89836     }
89837 });
89838 /**
89839  * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
89840  *
89841  * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
89842  * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
89843  * that has predictable points of interest at which you can update the control.
89844  *
89845  * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
89846  * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
89847  * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
89848  * for indicating intermediate progress.
89849  *
89850  *     @example
89851  *     var p = Ext.create('Ext.ProgressBar', {
89852  *        renderTo: Ext.getBody(),
89853  *        width: 300
89854  *     });
89855  *
89856  *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89857  *     p.wait({
89858  *         interval: 500, //bar will move fast!
89859  *         duration: 50000,
89860  *         increment: 15,
89861  *         text: 'Updating...',
89862  *         scope: this,
89863  *         fn: function(){
89864  *             p.updateText('Done!');
89865  *         }
89866  *     });
89867  */
89868 Ext.define('Ext.ProgressBar', {
89869     extend: 'Ext.Component',
89870     alias: 'widget.progressbar',
89871
89872     requires: [
89873         'Ext.Template',
89874         'Ext.CompositeElement',
89875         'Ext.TaskManager',
89876         'Ext.layout.component.ProgressBar'
89877     ],
89878
89879     uses: ['Ext.fx.Anim'],
89880
89881    /**
89882     * @cfg {Number} [value=0]
89883     * A floating point value between 0 and 1 (e.g., .5)
89884     */
89885
89886    /**
89887     * @cfg {String} [text='']
89888     * The progress bar text (defaults to '')
89889     */
89890
89891    /**
89892     * @cfg {String/HTMLElement/Ext.Element} textEl
89893     * The element to render the progress text to (defaults to the progress bar's internal text element)
89894     */
89895
89896    /**
89897     * @cfg {String} id
89898     * The progress bar element's id (defaults to an auto-generated id)
89899     */
89900
89901    /**
89902     * @cfg {String} [baseCls='x-progress']
89903     * The base CSS class to apply to the progress bar's wrapper element.
89904     */
89905     baseCls: Ext.baseCSSPrefix + 'progress',
89906
89907     config: {
89908         /**
89909         * @cfg {Boolean} animate
89910         * True to animate the progress bar during transitions
89911         */
89912         animate: false,
89913
89914         /**
89915          * @cfg {String} text
89916          * The text shown in the progress bar
89917          */
89918         text: ''
89919     },
89920
89921     // private
89922     waitTimer: null,
89923
89924     renderTpl: [
89925         '<div class="{baseCls}-text {baseCls}-text-back">',
89926             '<div>&#160;</div>',
89927         '</div>',
89928         '<div id="{id}-bar" class="{baseCls}-bar">',
89929             '<div class="{baseCls}-text">',
89930                 '<div>&#160;</div>',
89931             '</div>',
89932         '</div>'
89933     ],
89934
89935     componentLayout: 'progressbar',
89936
89937     // private
89938     initComponent: function() {
89939         this.callParent();
89940
89941         this.addChildEls('bar');
89942
89943         this.addEvents(
89944             /**
89945              * @event update
89946              * Fires after each update interval
89947              * @param {Ext.ProgressBar} this
89948              * @param {Number} value The current progress value
89949              * @param {String} text The current progress text
89950              */
89951             "update"
89952         );
89953     },
89954
89955     afterRender : function() {
89956         var me = this;
89957
89958         // This produces a composite w/2 el's (which is why we cannot use childEls or
89959         // renderSelectors):
89960         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
89961
89962         me.callParent(arguments);
89963
89964         if (me.value) {
89965             me.updateProgress(me.value, me.text);
89966         }
89967         else {
89968             me.updateText(me.text);
89969         }
89970     },
89971
89972     /**
89973      * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
89974      * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
89975      * it will never automatically reset -- you are responsible for determining when the progress is complete and
89976      * calling {@link #reset} to clear and/or hide the control.
89977      * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
89978      * @param {String} [text=''] The string to display in the progress text element
89979      * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
89980      * specified, the default for the class is used
89981      * @return {Ext.ProgressBar} this
89982      */
89983     updateProgress: function(value, text, animate) {
89984         var me = this,
89985             newWidth;
89986             
89987         me.value = value || 0;
89988         if (text) {
89989             me.updateText(text);
89990         }
89991         if (me.rendered && !me.isDestroyed) {
89992             if (me.isVisible(true)) {
89993                 newWidth = Math.floor(me.value * me.el.getWidth(true));
89994                 if (Ext.isForcedBorderBox) {
89995                     newWidth += me.bar.getBorderWidth("lr");
89996                 }
89997                 if (animate === true || (animate !== false && me.animate)) {
89998                     me.bar.stopAnimation();
89999                     me.bar.animate(Ext.apply({
90000                         to: {
90001                             width: newWidth + 'px'
90002                         }
90003                     }, me.animate));
90004                 } else {
90005                     me.bar.setWidth(newWidth);
90006                 }
90007             } else {
90008                 // force a layout when we're visible again
90009                 me.doComponentLayout();
90010             }
90011         }
90012         me.fireEvent('update', me, me.value, text);
90013         return me;
90014     },
90015
90016     /**
90017      * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
90018      * display the updated text.
90019      * @param {String} [text=''] The string to display in the progress text element
90020      * @return {Ext.ProgressBar} this
90021      */
90022     updateText: function(text) {
90023         var me = this;
90024         
90025         me.text = text;
90026         if (me.rendered) {
90027             me.textEl.update(me.text);
90028         }
90029         return me;
90030     },
90031
90032     applyText : function(text) {
90033         this.updateText(text);
90034     },
90035
90036     /**
90037      * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
90038      * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
90039      * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
90040      * {@link #reset}.
90041      *
90042      * Example usage:
90043      *
90044      *     var p = new Ext.ProgressBar({
90045      *        renderTo: 'my-el'
90046      *     });
90047      *
90048      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
90049      *     var p = Ext.create('Ext.ProgressBar', {
90050      *        renderTo: Ext.getBody(),
90051      *        width: 300
90052      *     });
90053      *
90054      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
90055      *     p.wait({
90056      *        interval: 500, //bar will move fast!
90057      *        duration: 50000,
90058      *        increment: 15,
90059      *        text: 'Updating...',
90060      *        scope: this,
90061      *        fn: function(){
90062      *           p.updateText('Done!');
90063      *        }
90064      *     });
90065      *
90066      *     //Or update indefinitely until some async action completes, then reset manually
90067      *     p.wait();
90068      *     myAction.on('complete', function(){
90069      *         p.reset();
90070      *         p.updateText('Done!');
90071      *     });
90072      *
90073      * @param {Object} config (optional) Configuration options
90074      * @param {Number} config.duration The length of time in milliseconds that the progress bar should
90075      * run before resetting itself (defaults to undefined, in which case it will run indefinitely
90076      * until reset is called)
90077      * @param {Number} config.interval The length of time in milliseconds between each progress update
90078      * (defaults to 1000 ms)
90079      * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
90080      * value is not specified, the default for the class is used.
90081      * @param {Number} config.increment The number of progress update segments to display within the
90082      * progress bar (defaults to 10).  If the bar reaches the end and is still updating, it will
90083      * automatically wrap back to the beginning.
90084      * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
90085      * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
90086      * updating.  The function will be called with no arguments.  This function will be ignored if
90087      * duration is not specified since in that case the progress bar can only be stopped programmatically,
90088      * so any required function should be called by the same code after it resets the progress bar.
90089      * @param {Object} config.scope The scope that is passed to the callback function (only applies when
90090      * duration and fn are both passed).
90091      * @return {Ext.ProgressBar} this
90092      */
90093     wait: function(o) {
90094         var me = this;
90095             
90096         if (!me.waitTimer) {
90097             scope = me;
90098             o = o || {};
90099             me.updateText(o.text);
90100             me.waitTimer = Ext.TaskManager.start({
90101                 run: function(i){
90102                     var inc = o.increment || 10;
90103                     i -= 1;
90104                     me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
90105                 },
90106                 interval: o.interval || 1000,
90107                 duration: o.duration,
90108                 onStop: function(){
90109                     if (o.fn) {
90110                         o.fn.apply(o.scope || me);
90111                     }
90112                     me.reset();
90113                 },
90114                 scope: scope
90115             });
90116         }
90117         return me;
90118     },
90119
90120     /**
90121      * Returns true if the progress bar is currently in a {@link #wait} operation
90122      * @return {Boolean} True if waiting, else false
90123      */
90124     isWaiting: function(){
90125         return this.waitTimer !== null;
90126     },
90127
90128     /**
90129      * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
90130      * (using the {@link #hideMode} property internally).
90131      * @param {Boolean} [hide=false] True to hide the progress bar.
90132      * @return {Ext.ProgressBar} this
90133      */
90134     reset: function(hide){
90135         var me = this;
90136         
90137         me.updateProgress(0);
90138         me.clearTimer();
90139         if (hide === true) {
90140             me.hide();
90141         }
90142         return me;
90143     },
90144
90145     // private
90146     clearTimer: function(){
90147         var me = this;
90148         
90149         if (me.waitTimer) {
90150             me.waitTimer.onStop = null; //prevent recursion
90151             Ext.TaskManager.stop(me.waitTimer);
90152             me.waitTimer = null;
90153         }
90154     },
90155
90156     onDestroy: function(){
90157         var me = this;
90158         
90159         me.clearTimer();
90160         if (me.rendered) {
90161             if (me.textEl.isComposite) {
90162                 me.textEl.clear();
90163             }
90164             Ext.destroyMembers(me, 'textEl', 'progressBar');
90165         }
90166         me.callParent();
90167     }
90168 });
90169
90170 /**
90171  * Private utility class that manages the internal Shadow cache
90172  * @private
90173  */
90174 Ext.define('Ext.ShadowPool', {
90175     singleton: true,
90176     requires: ['Ext.DomHelper'],
90177
90178     markup: function() {
90179         if (Ext.supports.CSS3BoxShadow) {
90180             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
90181         } else if (Ext.isIE) {
90182             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
90183         } else {
90184             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
90185                 '<div class="xst" role="presentation">' +
90186                     '<div class="xstl" role="presentation"></div>' +
90187                     '<div class="xstc" role="presentation"></div>' +
90188                     '<div class="xstr" role="presentation"></div>' +
90189                 '</div>' +
90190                 '<div class="xsc" role="presentation">' +
90191                     '<div class="xsml" role="presentation"></div>' +
90192                     '<div class="xsmc" role="presentation"></div>' +
90193                     '<div class="xsmr" role="presentation"></div>' +
90194                 '</div>' +
90195                 '<div class="xsb" role="presentation">' +
90196                     '<div class="xsbl" role="presentation"></div>' +
90197                     '<div class="xsbc" role="presentation"></div>' +
90198                     '<div class="xsbr" role="presentation"></div>' +
90199                 '</div>' +
90200             '</div>';
90201         }
90202     }(),
90203
90204     shadows: [],
90205
90206     pull: function() {
90207         var sh = this.shadows.shift();
90208         if (!sh) {
90209             sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
90210             sh.autoBoxAdjust = false;
90211         }
90212         return sh;
90213     },
90214
90215     push: function(sh) {
90216         this.shadows.push(sh);
90217     },
90218     
90219     reset: function() {
90220         Ext.Array.each(this.shadows, function(shadow) {
90221             shadow.remove();
90222         });
90223         this.shadows = [];
90224     }
90225 });
90226 /**
90227  * @class Ext.Shadow
90228  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
90229  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
90230  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
90231  */
90232 Ext.define('Ext.Shadow', {
90233     requires: ['Ext.ShadowPool'],
90234
90235     /**
90236      * Creates new Shadow.
90237      * @param {Object} config (optional) Config object.
90238      */
90239     constructor: function(config) {
90240         var me = this,
90241             adjusts = {
90242                 h: 0
90243             },
90244             offset,
90245             rad;
90246         
90247         Ext.apply(me, config);
90248         if (!Ext.isString(me.mode)) {
90249             me.mode = me.defaultMode;
90250         }
90251         offset = me.offset;
90252         rad = Math.floor(offset / 2);
90253         me.opacity = 50;
90254         switch (me.mode.toLowerCase()) {
90255             // all this hideous nonsense calculates the various offsets for shadows
90256             case "drop":
90257                 if (Ext.supports.CSS3BoxShadow) {
90258                     adjusts.w = adjusts.h = -offset;
90259                     adjusts.l = adjusts.t = offset;
90260                 } else {
90261                     adjusts.w = 0;
90262                     adjusts.l = adjusts.t = offset;
90263                     adjusts.t -= 1;
90264                     if (Ext.isIE) {
90265                         adjusts.l -= offset + rad;
90266                         adjusts.t -= offset + rad;
90267                         adjusts.w -= rad;
90268                         adjusts.h -= rad;
90269                         adjusts.t += 1;
90270                     }
90271                 }
90272                 break;
90273             case "sides":
90274                 if (Ext.supports.CSS3BoxShadow) {
90275                     adjusts.h -= offset;
90276                     adjusts.t = offset;
90277                     adjusts.l = adjusts.w = 0;
90278                 } else {
90279                     adjusts.w = (offset * 2);
90280                     adjusts.l = -offset;
90281                     adjusts.t = offset - 1;
90282                     if (Ext.isIE) {
90283                         adjusts.l -= (offset - rad);
90284                         adjusts.t -= offset + rad;
90285                         adjusts.l += 1;
90286                         adjusts.w -= (offset - rad) * 2;
90287                         adjusts.w -= rad + 1;
90288                         adjusts.h -= 1;
90289                     }
90290                 }
90291                 break;
90292             case "frame":
90293                 if (Ext.supports.CSS3BoxShadow) {
90294                     adjusts.l = adjusts.w = adjusts.t = 0;
90295                 } else {
90296                     adjusts.w = adjusts.h = (offset * 2);
90297                     adjusts.l = adjusts.t = -offset;
90298                     adjusts.t += 1;
90299                     adjusts.h -= 2;
90300                     if (Ext.isIE) {
90301                         adjusts.l -= (offset - rad);
90302                         adjusts.t -= (offset - rad);
90303                         adjusts.l += 1;
90304                         adjusts.w -= (offset + rad + 1);
90305                         adjusts.h -= (offset + rad);
90306                         adjusts.h += 1;
90307                     }
90308                     break;
90309                 }
90310         }
90311         me.adjusts = adjusts;
90312     },
90313
90314     /**
90315      * @cfg {String} mode
90316      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
90317      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
90318      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
90319      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
90320      * </ul></div>
90321      */
90322     /**
90323      * @cfg {Number} offset
90324      * The number of pixels to offset the shadow from the element
90325      */
90326     offset: 4,
90327
90328     // private
90329     defaultMode: "drop",
90330
90331     /**
90332      * Displays the shadow under the target element
90333      * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
90334      */
90335     show: function(target) {
90336         var me = this,
90337             index;
90338         
90339         target = Ext.get(target);
90340         if (!me.el) {
90341             me.el = Ext.ShadowPool.pull();
90342             if (me.el.dom.nextSibling != target.dom) {
90343                 me.el.insertBefore(target);
90344             }
90345         }
90346         index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
90347         me.el.setStyle("z-index", me.zIndex || index);
90348         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
90349             me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
90350         }
90351         me.realign(
90352             target.getLeft(true),
90353             target.getTop(true),
90354             target.dom.offsetWidth,
90355             target.dom.offsetHeight
90356         );
90357         me.el.dom.style.display = "block";
90358     },
90359
90360     /**
90361      * Returns true if the shadow is visible, else false
90362      */
90363     isVisible: function() {
90364         return this.el ? true: false;
90365     },
90366
90367     /**
90368      * Direct alignment when values are already available. Show must be called at least once before
90369      * calling this method to ensure it is initialized.
90370      * @param {Number} left The target element left position
90371      * @param {Number} top The target element top position
90372      * @param {Number} width The target element width
90373      * @param {Number} height The target element height
90374      */
90375     realign: function(l, t, targetWidth, targetHeight) {
90376         if (!this.el) {
90377             return;
90378         }
90379         var adjusts = this.adjusts,
90380             d = this.el.dom,
90381             targetStyle = d.style,
90382             shadowWidth,
90383             shadowHeight,
90384             cn,
90385             sww, 
90386             sws, 
90387             shs;
90388
90389         targetStyle.left = (l + adjusts.l) + "px";
90390         targetStyle.top = (t + adjusts.t) + "px";
90391         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
90392         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
90393         sws = shadowWidth + "px";
90394         shs = shadowHeight + "px";
90395         if (targetStyle.width != sws || targetStyle.height != shs) {
90396             targetStyle.width = sws;
90397             targetStyle.height = shs;
90398             if (Ext.supports.CSS3BoxShadow) {
90399                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
90400             } else {
90401
90402                 // Adjust the 9 point framed element to poke out on the required sides
90403                 if (!Ext.isIE) {
90404                     cn = d.childNodes;
90405                     sww = Math.max(0, (shadowWidth - 12)) + "px";
90406                     cn[0].childNodes[1].style.width = sww;
90407                     cn[1].childNodes[1].style.width = sww;
90408                     cn[2].childNodes[1].style.width = sww;
90409                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
90410                 }
90411             }
90412         }
90413     },
90414
90415     /**
90416      * Hides this shadow
90417      */
90418     hide: function() {
90419         var me = this;
90420         
90421         if (me.el) {
90422             me.el.dom.style.display = "none";
90423             Ext.ShadowPool.push(me.el);
90424             delete me.el;
90425         }
90426     },
90427
90428     /**
90429      * Adjust the z-index of this shadow
90430      * @param {Number} zindex The new z-index
90431      */
90432     setZIndex: function(z) {
90433         this.zIndex = z;
90434         if (this.el) {
90435             this.el.setStyle("z-index", z);
90436         }
90437     },
90438     
90439     /**
90440      * Sets the opacity of the shadow
90441      * @param {Number} opacity The opacity
90442      */
90443     setOpacity: function(opacity){
90444         if (this.el) {
90445             if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
90446                 opacity = Math.floor(opacity * 100 / 2) / 100;
90447             }
90448             this.opacity = opacity;
90449             this.el.setOpacity(opacity);
90450         }
90451     }
90452 });
90453 /**
90454  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
90455  * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
90456  * primary button action, but any custom handler can provide the arrowclick implementation.  Example usage:
90457  *
90458  *     @example
90459  *     // display a dropdown menu:
90460  *     Ext.create('Ext.button.Split', {
90461  *         renderTo: Ext.getBody(),
90462  *         text: 'Options',
90463  *         // handle a click on the button itself
90464  *         handler: function() {
90465  *             alert("The button was clicked");
90466  *         },
90467  *         menu: new Ext.menu.Menu({
90468  *             items: [
90469  *                 // these will render as dropdown menu items when the arrow is clicked:
90470  *                 {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
90471  *                 {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
90472  *             ]
90473  *         })
90474  *     });
90475  *
90476  * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
90477  * arrow is clicked:
90478  *
90479  *     Ext.create('Ext.button.Split', {
90480  *         renderTo: 'button-ct',
90481  *         text: 'Options',
90482  *         handler: optionsHandler,
90483  *         arrowHandler: myCustomHandler
90484  *     });
90485  *
90486  */
90487 Ext.define('Ext.button.Split', {
90488
90489     /* Begin Definitions */
90490     alias: 'widget.splitbutton',
90491
90492     extend: 'Ext.button.Button',
90493     alternateClassName: 'Ext.SplitButton',
90494     /* End Definitions */
90495     
90496     /**
90497      * @cfg {Function} arrowHandler
90498      * A function called when the arrow button is clicked (can be used instead of click event)
90499      */
90500     /**
90501      * @cfg {String} arrowTooltip
90502      * The title attribute of the arrow
90503      */
90504
90505     // private
90506     arrowCls      : 'split',
90507     split         : true,
90508
90509     // private
90510     initComponent : function(){
90511         this.callParent();
90512         /**
90513          * @event arrowclick
90514          * Fires when this button's arrow is clicked.
90515          * @param {Ext.button.Split} this
90516          * @param {Event} e The click event
90517          */
90518         this.addEvents("arrowclick");
90519     },
90520
90521     /**
90522      * Sets this button's arrow click handler.
90523      * @param {Function} handler The function to call when the arrow is clicked
90524      * @param {Object} scope (optional) Scope for the function passed above
90525      */
90526     setArrowHandler : function(handler, scope){
90527         this.arrowHandler = handler;
90528         this.scope = scope;
90529     },
90530
90531     // private
90532     onClick : function(e, t) {
90533         var me = this;
90534         
90535         e.preventDefault();
90536         if (!me.disabled) {
90537             if (me.overMenuTrigger) {
90538                 me.maybeShowMenu();
90539                 me.fireEvent("arrowclick", me, e);
90540                 if (me.arrowHandler) {
90541                     me.arrowHandler.call(me.scope || me, me, e);
90542                 }
90543             } else {
90544                 me.doToggle();
90545                 me.fireHandler();
90546             }
90547         }
90548     }
90549 });
90550 /**
90551  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
90552  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
90553  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
90554  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
90555  *
90556  *     @example
90557  *     Ext.create('Ext.button.Cycle', {
90558  *         showText: true,
90559  *         prependText: 'View as ',
90560  *         renderTo: Ext.getBody(),
90561  *         menu: {
90562  *             id: 'view-type-menu',
90563  *             items: [{
90564  *                 text: 'text only',
90565  *                 iconCls: 'view-text',
90566  *                 checked: true
90567  *             },{
90568  *                 text: 'HTML',
90569  *                 iconCls: 'view-html'
90570  *             }]
90571  *         },
90572  *         changeHandler: function(cycleBtn, activeItem) {
90573  *             Ext.Msg.alert('Change View', activeItem.text);
90574  *         }
90575  *     });
90576  */
90577 Ext.define('Ext.button.Cycle', {
90578
90579     /* Begin Definitions */
90580
90581     alias: 'widget.cycle',
90582
90583     extend: 'Ext.button.Split',
90584     alternateClassName: 'Ext.CycleButton',
90585
90586     /* End Definitions */
90587
90588     /**
90589      * @cfg {Object[]} items
90590      * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
90591      * `{text:'Foo', iconCls:'foo-icon'}`)
90592      * 
90593      * @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
90594      * {@link Ext.menu.CheckItem CheckItems}.
90595      */
90596     /**
90597      * @cfg {Boolean} [showText=false]
90598      * True to display the active item's text as the button text. The Button will show its
90599      * configured {@link #text} if this config is omitted.
90600      */
90601     /**
90602      * @cfg {String} [prependText='']
90603      * A static string to prepend before the active item's text when displayed as the button's text (only applies when
90604      * showText = true).
90605      */
90606     /**
90607      * @cfg {Function} changeHandler
90608      * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
90609      * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
90610      * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
90611      * item)
90612      */
90613     /**
90614      * @cfg {String} forceIcon
90615      * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
90616      * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
90617      * button's icon to match the selected item's icon on change.
90618      */
90619     /**
90620      * @property {Ext.menu.Menu} menu
90621      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
90622      * available choices.
90623      */
90624
90625     // private
90626     getButtonText: function(item) {
90627         var me = this,
90628             text = '';
90629
90630         if (item && me.showText === true) {
90631             if (me.prependText) {
90632                 text += me.prependText;
90633             }
90634             text += item.text;
90635             return text;
90636         }
90637         return me.text;
90638     },
90639
90640     /**
90641      * Sets the button's active menu item.
90642      * @param {Ext.menu.CheckItem} item The item to activate
90643      * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
90644      */
90645     setActiveItem: function(item, suppressEvent) {
90646         var me = this;
90647
90648         if (!Ext.isObject(item)) {
90649             item = me.menu.getComponent(item);
90650         }
90651         if (item) {
90652             if (!me.rendered) {
90653                 me.text = me.getButtonText(item);
90654                 me.iconCls = item.iconCls;
90655             } else {
90656                 me.setText(me.getButtonText(item));
90657                 me.setIconCls(item.iconCls);
90658             }
90659             me.activeItem = item;
90660             if (!item.checked) {
90661                 item.setChecked(true, false);
90662             }
90663             if (me.forceIcon) {
90664                 me.setIconCls(me.forceIcon);
90665             }
90666             if (!suppressEvent) {
90667                 me.fireEvent('change', me, item);
90668             }
90669         }
90670     },
90671
90672     /**
90673      * Gets the currently active menu item.
90674      * @return {Ext.menu.CheckItem} The active item
90675      */
90676     getActiveItem: function() {
90677         return this.activeItem;
90678     },
90679
90680     // private
90681     initComponent: function() {
90682         var me = this,
90683             checked = 0,
90684             items;
90685
90686         me.addEvents(
90687             /**
90688              * @event change
90689              * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
90690              * set on this CycleButton, it will be called instead on active item change and this change event will not
90691              * be fired.
90692              * @param {Ext.button.Cycle} this
90693              * @param {Ext.menu.CheckItem} item The menu item that was selected
90694              */
90695             "change"
90696         );
90697
90698         if (me.changeHandler) {
90699             me.on('change', me.changeHandler, me.scope || me);
90700             delete me.changeHandler;
90701         }
90702
90703         // Allow them to specify a menu config which is a standard Button config.
90704         // Remove direct use of "items" in 5.0.
90705         items = (me.menu.items||[]).concat(me.items||[]);
90706         me.menu = Ext.applyIf({
90707             cls: Ext.baseCSSPrefix + 'cycle-menu',
90708             items: []
90709         }, me.menu);
90710
90711         // Convert all items to CheckItems
90712         Ext.each(items, function(item, i) {
90713             item = Ext.applyIf({
90714                 group: me.id,
90715                 itemIndex: i,
90716                 checkHandler: me.checkHandler,
90717                 scope: me,
90718                 checked: item.checked || false
90719             }, item);
90720             me.menu.items.push(item);
90721             if (item.checked) {
90722                 checked = i;
90723             }
90724         });
90725         me.itemCount = me.menu.items.length;
90726         me.callParent(arguments);
90727         me.on('click', me.toggleSelected, me);
90728         me.setActiveItem(checked, me);
90729
90730         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
90731         if (me.width && me.showText) {
90732             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
90733         }
90734     },
90735
90736     // private
90737     checkHandler: function(item, pressed) {
90738         if (pressed) {
90739             this.setActiveItem(item);
90740         }
90741     },
90742
90743     /**
90744      * This is normally called internally on button click, but can be called externally to advance the button's active
90745      * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
90746      * item will be set to the first item in the menu.
90747      */
90748     toggleSelected: function() {
90749         var me = this,
90750             m = me.menu,
90751             checkItem;
90752
90753         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
90754         checkItem.setChecked(true);
90755     }
90756 });
90757 /**
90758  * Provides a container for arranging a group of related Buttons in a tabular manner.
90759  *
90760  *     @example
90761  *     Ext.create('Ext.panel.Panel', {
90762  *         title: 'Panel with ButtonGroup',
90763  *         width: 300,
90764  *         height:200,
90765  *         renderTo: document.body,
90766  *         bodyPadding: 10,
90767  *         html: 'HTML Panel Content',
90768  *         tbar: [{
90769  *             xtype: 'buttongroup',
90770  *             columns: 3,
90771  *             title: 'Clipboard',
90772  *             items: [{
90773  *                 text: 'Paste',
90774  *                 scale: 'large',
90775  *                 rowspan: 3,
90776  *                 iconCls: 'add',
90777  *                 iconAlign: 'top',
90778  *                 cls: 'btn-as-arrow'
90779  *             },{
90780  *                 xtype:'splitbutton',
90781  *                 text: 'Menu Button',
90782  *                 scale: 'large',
90783  *                 rowspan: 3,
90784  *                 iconCls: 'add',
90785  *                 iconAlign: 'top',
90786  *                 arrowAlign:'bottom',
90787  *                 menu: [{ text: 'Menu Item 1' }]
90788  *             },{
90789  *                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
90790  *             },{
90791  *                 text: 'Copy', iconCls: 'add16'
90792  *             },{
90793  *                 text: 'Format', iconCls: 'add16'
90794  *             }]
90795  *         }]
90796  *     });
90797  *
90798  */
90799 Ext.define('Ext.container.ButtonGroup', {
90800     extend: 'Ext.panel.Panel',
90801     alias: 'widget.buttongroup',
90802     alternateClassName: 'Ext.ButtonGroup',
90803
90804     /**
90805      * @cfg {Number} columns The `columns` configuration property passed to the
90806      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
90807      */
90808
90809     /**
90810      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
90811      */
90812     baseCls: Ext.baseCSSPrefix + 'btn-group',
90813
90814     /**
90815      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
90816      */
90817     layout: {
90818         type: 'table'
90819     },
90820
90821     defaultType: 'button',
90822
90823     /**
90824      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
90825      */
90826     frame: true,
90827
90828     frameHeader: false,
90829
90830     internalDefaults: {removeMode: 'container', hideParent: true},
90831
90832     initComponent : function(){
90833         // Copy the component's columns config to the layout if specified
90834         var me = this,
90835             cols = me.columns;
90836
90837         me.noTitleCls = me.baseCls + '-notitle';
90838         if (cols) {
90839             me.layout = Ext.apply({}, {columns: cols}, me.layout);
90840         }
90841
90842         if (!me.title) {
90843             me.addCls(me.noTitleCls);
90844         }
90845         me.callParent(arguments);
90846     },
90847
90848     afterLayout: function() {
90849         var me = this;
90850
90851         me.callParent(arguments);
90852
90853         // Pugly hack for a pugly browser:
90854         // If not an explicitly set width, then size the width to match the inner table
90855         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
90856             var t = me.getTargetEl();
90857             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
90858         }
90859
90860         // IE7 needs a forced repaint to make the top framing div expand to full width
90861         if (Ext.isIE7) {
90862             me.el.repaint();
90863         }
90864     },
90865
90866     afterRender: function() {
90867         var me = this;
90868
90869         //we need to add an addition item in here so the ButtonGroup title is centered
90870         if (me.header) {
90871             // Header text cannot flex, but must be natural size if it's being centered
90872             delete me.header.items.items[0].flex;
90873
90874             // For Centering, surround the text with two flex:1 spacers.
90875             me.suspendLayout = true;
90876             me.header.insert(1, {
90877                 xtype: 'component',
90878                 ui   : me.ui,
90879                 flex : 1
90880             });
90881             me.header.insert(0, {
90882                 xtype: 'component',
90883                 ui   : me.ui,
90884                 flex : 1
90885             });
90886             me.suspendLayout = false;
90887         }
90888
90889         me.callParent(arguments);
90890     },
90891
90892     // private
90893     onBeforeAdd: function(component) {
90894         if (component.is('button')) {
90895             component.ui = component.ui + '-toolbar';
90896         }
90897         this.callParent(arguments);
90898     },
90899
90900     //private
90901     applyDefaults: function(c) {
90902         if (!Ext.isString(c)) {
90903             c = this.callParent(arguments);
90904             var d = this.internalDefaults;
90905             if (c.events) {
90906                 Ext.applyIf(c.initialConfig, d);
90907                 Ext.apply(c, d);
90908             } else {
90909                 Ext.applyIf(c, d);
90910             }
90911         }
90912         return c;
90913     }
90914
90915     /**
90916      * @cfg {Array} tools  @hide
90917      */
90918     /**
90919      * @cfg {Boolean} collapsible  @hide
90920      */
90921     /**
90922      * @cfg {Boolean} collapseMode  @hide
90923      */
90924     /**
90925      * @cfg {Boolean} animCollapse  @hide
90926      */
90927     /**
90928      * @cfg {Boolean} closable  @hide
90929      */
90930 });
90931
90932 /**
90933  * A specialized container representing the viewable application area (the browser viewport).
90934  *
90935  * The Viewport renders itself to the document body, and automatically sizes itself to the size of
90936  * the browser viewport and manages window resizing. There may only be one Viewport created
90937  * in a page.
90938  *
90939  * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
90940  * on its child Components if you configure it with a {@link #layout}.
90941  *
90942  * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
90943  * required layout is simpler, a different layout should be chosen.
90944  *
90945  * For example, to simply make a single child item occupy all available space, use
90946  * {@link Ext.layout.container.Fit fit layout}.
90947  *
90948  * To display one "active" item at full size from a choice of several child items, use
90949  * {@link Ext.layout.container.Card card layout}.
90950  *
90951  * Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
90952  * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
90953  * method of any of its child Panels may themselves have a layout.
90954  *
90955  * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
90956  * for scrolling if needed using the {@link #autoScroll} config.
90957  *
90958  * An example showing a classic application border layout:
90959  *
90960  *     @example
90961  *     Ext.create('Ext.container.Viewport', {
90962  *         layout: 'border',
90963  *         items: [{
90964  *             region: 'north',
90965  *             html: '<h1 class="x-panel-header">Page Title</h1>',
90966  *             autoHeight: true,
90967  *             border: false,
90968  *             margins: '0 0 5 0'
90969  *         }, {
90970  *             region: 'west',
90971  *             collapsible: true,
90972  *             title: 'Navigation',
90973  *             width: 150
90974  *             // could use a TreePanel or AccordionLayout for navigational items
90975  *         }, {
90976  *             region: 'south',
90977  *             title: 'South Panel',
90978  *             collapsible: true,
90979  *             html: 'Information goes here',
90980  *             split: true,
90981  *             height: 100,
90982  *             minHeight: 100
90983  *         }, {
90984  *             region: 'east',
90985  *             title: 'East Panel',
90986  *             collapsible: true,
90987  *             split: true,
90988  *             width: 150
90989  *         }, {
90990  *             region: 'center',
90991  *             xtype: 'tabpanel', // TabPanel itself has no title
90992  *             activeTab: 0,      // First tab active by default
90993  *             items: {
90994  *                 title: 'Default Tab',
90995  *                 html: 'The first tab\'s content. Others may be added dynamically'
90996  *             }
90997  *         }]
90998  *     });
90999  */
91000 Ext.define('Ext.container.Viewport', {
91001     extend: 'Ext.container.Container',
91002     alias: 'widget.viewport',
91003     requires: ['Ext.EventManager'],
91004     alternateClassName: 'Ext.Viewport',
91005
91006     // Privatize config options which, if used, would interfere with the
91007     // correct operation of the Viewport as the sole manager of the
91008     // layout of the document body.
91009
91010     /**
91011      * @cfg {String/HTMLElement/Ext.Element} applyTo
91012      * Not applicable.
91013      */
91014
91015     /**
91016      * @cfg {Boolean} allowDomMove
91017      * Not applicable.
91018      */
91019
91020     /**
91021      * @cfg {Boolean} hideParent
91022      * Not applicable.
91023      */
91024
91025     /**
91026      * @cfg {String/HTMLElement/Ext.Element} renderTo
91027      * Not applicable. Always renders to document body.
91028      */
91029
91030     /**
91031      * @cfg {Boolean} hideParent
91032      * Not applicable.
91033      */
91034
91035     /**
91036      * @cfg {Number} height
91037      * Not applicable. Sets itself to viewport width.
91038      */
91039
91040     /**
91041      * @cfg {Number} width
91042      * Not applicable. Sets itself to viewport height.
91043      */
91044
91045     /**
91046      * @cfg {Boolean} autoHeight
91047      * Not applicable.
91048      */
91049
91050     /**
91051      * @cfg {Boolean} autoWidth
91052      * Not applicable.
91053      */
91054
91055     /**
91056      * @cfg {Boolean} deferHeight
91057      * Not applicable.
91058      */
91059
91060     /**
91061      * @cfg {Boolean} monitorResize
91062      * Not applicable.
91063      */
91064
91065     isViewport: true,
91066
91067     ariaRole: 'application',
91068
91069     initComponent : function() {
91070         var me = this,
91071             html = Ext.fly(document.body.parentNode),
91072             el;
91073         me.callParent(arguments);
91074         html.addCls(Ext.baseCSSPrefix + 'viewport');
91075         if (me.autoScroll) {
91076             html.setStyle('overflow', 'auto');
91077         }
91078         me.el = el = Ext.getBody();
91079         el.setHeight = Ext.emptyFn;
91080         el.setWidth = Ext.emptyFn;
91081         el.setSize = Ext.emptyFn;
91082         el.dom.scroll = 'no';
91083         me.allowDomMove = false;
91084         Ext.EventManager.onWindowResize(me.fireResize, me);
91085         me.renderTo = me.el;
91086         me.width = Ext.Element.getViewportWidth();
91087         me.height = Ext.Element.getViewportHeight();
91088     },
91089
91090     fireResize : function(w, h){
91091         // setSize is the single entry point to layouts
91092         this.setSize(w, h);
91093     }
91094 });
91095
91096 /*
91097  * This is a derivative of the similarly named class in the YUI Library.
91098  * The original license:
91099  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
91100  * Code licensed under the BSD License:
91101  * http://developer.yahoo.net/yui/license.txt
91102  */
91103
91104
91105 /**
91106  * @class Ext.dd.DDTarget
91107  * @extends Ext.dd.DragDrop
91108  * A DragDrop implementation that does not move, but can be a drop
91109  * target.  You would get the same result by simply omitting implementation
91110  * for the event callbacks, but this way we reduce the processing cost of the
91111  * event listener and the callbacks.
91112  */
91113 Ext.define('Ext.dd.DDTarget', {
91114     extend: 'Ext.dd.DragDrop',
91115
91116     /**
91117      * Creates new DDTarget.
91118      * @param {String} id the id of the element that is a drop target
91119      * @param {String} sGroup the group of related DragDrop objects
91120      * @param {Object} config an object containing configurable attributes.
91121      * Valid properties for DDTarget in addition to those in DragDrop: none.
91122      */
91123     constructor: function(id, sGroup, config) {
91124         if (id) {
91125             this.initTarget(id, sGroup, config);
91126         }
91127     },
91128
91129     /**
91130      * @hide
91131      * Overridden and disabled. A DDTarget does not support being dragged.
91132      * @method
91133      */
91134     getDragEl: Ext.emptyFn,
91135     /**
91136      * @hide
91137      * Overridden and disabled. A DDTarget does not support being dragged.
91138      * @method
91139      */
91140     isValidHandleChild: Ext.emptyFn,
91141     /**
91142      * @hide
91143      * Overridden and disabled. A DDTarget does not support being dragged.
91144      * @method
91145      */
91146     startDrag: Ext.emptyFn,
91147     /**
91148      * @hide
91149      * Overridden and disabled. A DDTarget does not support being dragged.
91150      * @method
91151      */
91152     endDrag: Ext.emptyFn,
91153     /**
91154      * @hide
91155      * Overridden and disabled. A DDTarget does not support being dragged.
91156      * @method
91157      */
91158     onDrag: Ext.emptyFn,
91159     /**
91160      * @hide
91161      * Overridden and disabled. A DDTarget does not support being dragged.
91162      * @method
91163      */
91164     onDragDrop: Ext.emptyFn,
91165     /**
91166      * @hide
91167      * Overridden and disabled. A DDTarget does not support being dragged.
91168      * @method
91169      */
91170     onDragEnter: Ext.emptyFn,
91171     /**
91172      * @hide
91173      * Overridden and disabled. A DDTarget does not support being dragged.
91174      * @method
91175      */
91176     onDragOut: Ext.emptyFn,
91177     /**
91178      * @hide
91179      * Overridden and disabled. A DDTarget does not support being dragged.
91180      * @method
91181      */
91182     onDragOver: Ext.emptyFn,
91183     /**
91184      * @hide
91185      * Overridden and disabled. A DDTarget does not support being dragged.
91186      * @method
91187      */
91188     onInvalidDrop: Ext.emptyFn,
91189     /**
91190      * @hide
91191      * Overridden and disabled. A DDTarget does not support being dragged.
91192      * @method
91193      */
91194     onMouseDown: Ext.emptyFn,
91195     /**
91196      * @hide
91197      * Overridden and disabled. A DDTarget does not support being dragged.
91198      * @method
91199      */
91200     onMouseUp: Ext.emptyFn,
91201     /**
91202      * @hide
91203      * Overridden and disabled. A DDTarget does not support being dragged.
91204      * @method
91205      */
91206     setXConstraint: Ext.emptyFn,
91207     /**
91208      * @hide
91209      * Overridden and disabled. A DDTarget does not support being dragged.
91210      * @method
91211      */
91212     setYConstraint: Ext.emptyFn,
91213     /**
91214      * @hide
91215      * Overridden and disabled. A DDTarget does not support being dragged.
91216      * @method
91217      */
91218     resetConstraints: Ext.emptyFn,
91219     /**
91220      * @hide
91221      * Overridden and disabled. A DDTarget does not support being dragged.
91222      * @method
91223      */
91224     clearConstraints: Ext.emptyFn,
91225     /**
91226      * @hide
91227      * Overridden and disabled. A DDTarget does not support being dragged.
91228      * @method
91229      */
91230     clearTicks: Ext.emptyFn,
91231     /**
91232      * @hide
91233      * Overridden and disabled. A DDTarget does not support being dragged.
91234      * @method
91235      */
91236     setInitPosition: Ext.emptyFn,
91237     /**
91238      * @hide
91239      * Overridden and disabled. A DDTarget does not support being dragged.
91240      * @method
91241      */
91242     setDragElId: Ext.emptyFn,
91243     /**
91244      * @hide
91245      * Overridden and disabled. A DDTarget does not support being dragged.
91246      * @method
91247      */
91248     setHandleElId: Ext.emptyFn,
91249     /**
91250      * @hide
91251      * Overridden and disabled. A DDTarget does not support being dragged.
91252      * @method
91253      */
91254     setOuterHandleElId: Ext.emptyFn,
91255     /**
91256      * @hide
91257      * Overridden and disabled. A DDTarget does not support being dragged.
91258      * @method
91259      */
91260     addInvalidHandleClass: Ext.emptyFn,
91261     /**
91262      * @hide
91263      * Overridden and disabled. A DDTarget does not support being dragged.
91264      * @method
91265      */
91266     addInvalidHandleId: Ext.emptyFn,
91267     /**
91268      * @hide
91269      * Overridden and disabled. A DDTarget does not support being dragged.
91270      * @method
91271      */
91272     addInvalidHandleType: Ext.emptyFn,
91273     /**
91274      * @hide
91275      * Overridden and disabled. A DDTarget does not support being dragged.
91276      * @method
91277      */
91278     removeInvalidHandleClass: Ext.emptyFn,
91279     /**
91280      * @hide
91281      * Overridden and disabled. A DDTarget does not support being dragged.
91282      * @method
91283      */
91284     removeInvalidHandleId: Ext.emptyFn,
91285     /**
91286      * @hide
91287      * Overridden and disabled. A DDTarget does not support being dragged.
91288      * @method
91289      */
91290     removeInvalidHandleType: Ext.emptyFn,
91291
91292     toString: function() {
91293         return ("DDTarget " + this.id);
91294     }
91295 });
91296 /**
91297  * @class Ext.dd.DragTracker
91298  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
91299  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
91300  * an element that can be dragged around to change the Slider's value.
91301  * DragTracker provides a series of template methods that should be overridden to provide functionality
91302  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
91303  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
91304  */
91305 Ext.define('Ext.dd.DragTracker', {
91306
91307     uses: ['Ext.util.Region'],
91308
91309     mixins: {
91310         observable: 'Ext.util.Observable'
91311     },
91312
91313     /**
91314      * @property {Boolean} active
91315      * Read-only property indicated whether the user is currently dragging this
91316      * tracker.
91317      */
91318     active: false,
91319
91320     /**
91321      * @property {HTMLElement} dragTarget
91322      * <p><b>Only valid during drag operations. Read-only.</b></p>
91323      * <p>The element being dragged.</p>
91324      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
91325      */
91326
91327     /**
91328      * @cfg {Boolean} trackOver
91329      * <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>
91330      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
91331      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
91332      */
91333     trackOver: false,
91334
91335     /**
91336      * @cfg {String} overCls
91337      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
91338      * when a delegate element) is mouseovered.</p>
91339      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
91340      */
91341
91342     /**
91343      * @cfg {Ext.util.Region/Ext.Element} constrainTo
91344      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
91345      * the result of the {@link #getOffset} call.</p>
91346      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
91347      */
91348
91349     /**
91350      * @cfg {Number} tolerance
91351      * Number of pixels the drag target must be moved before dragging is
91352      * considered to have started. Defaults to <code>5</code>.
91353      */
91354     tolerance: 5,
91355
91356     /**
91357      * @cfg {Boolean/Number} autoStart
91358      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
91359      * Specify a Number for the number of milliseconds to defer trigger start.
91360      */
91361     autoStart: false,
91362
91363     /**
91364      * @cfg {String} delegate
91365      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
91366      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
91367      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
91368      */
91369
91370     /**
91371      * @cfg {Boolean} preventDefault
91372      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
91373      */
91374
91375     /**
91376      * @cfg {Boolean} stopEvent
91377      * 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>.
91378      */
91379
91380     constructor : function(config){
91381         Ext.apply(this, config);
91382         this.addEvents(
91383             /**
91384              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
91385              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
91386              * used, when the mouse enters a delegate element).</p>
91387              * @param {Object} this
91388              * @param {Object} e event object
91389              * @param {HTMLElement} target The element mouseovered.
91390              */
91391             'mouseover',
91392
91393             /**
91394              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
91395              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
91396              * used, when the mouse exits a delegate element).</p>
91397              * @param {Object} this
91398              * @param {Object} e event object
91399              */
91400             'mouseout',
91401
91402             /**
91403              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
91404              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
91405              * the {@link #autoStart} timer fires.</p>
91406              * <p>Return false to veto the drag operation.</p>
91407              * @param {Object} this
91408              * @param {Object} e event object
91409              */
91410             'mousedown',
91411
91412             /**
91413              * @event mouseup
91414              * @param {Object} this
91415              * @param {Object} e event object
91416              */
91417             'mouseup',
91418
91419             /**
91420              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
91421              * @param {Object} this
91422              * @param {Object} e event object
91423              */
91424             'mousemove',
91425
91426             /**
91427              * @event beforestart
91428              * @param {Object} this
91429              * @param {Object} e event object
91430              */
91431             'beforedragstart',
91432
91433             /**
91434              * @event dragstart
91435              * @param {Object} this
91436              * @param {Object} e event object
91437              */
91438             'dragstart',
91439
91440             /**
91441              * @event dragend
91442              * @param {Object} this
91443              * @param {Object} e event object
91444              */
91445             'dragend',
91446
91447             /**
91448              * @event drag
91449              * @param {Object} this
91450              * @param {Object} e event object
91451              */
91452             'drag'
91453         );
91454
91455         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
91456
91457         if (this.el) {
91458             this.initEl(this.el);
91459         }
91460
91461         // Dont pass the config so that it is not applied to 'this' again
91462         this.mixins.observable.constructor.call(this);
91463         if (this.disabled) {
91464             this.disable();
91465         }
91466
91467     },
91468
91469     /**
91470      * Initializes the DragTracker on a given element.
91471      * @param {Ext.Element/HTMLElement} el The element
91472      */
91473     initEl: function(el) {
91474         this.el = Ext.get(el);
91475
91476         // The delegate option may also be an element on which to listen
91477         this.handle = Ext.get(this.delegate);
91478
91479         // If delegate specified an actual element to listen on, we do not use the delegate listener option
91480         this.delegate = this.handle ? undefined : this.delegate;
91481
91482         if (!this.handle) {
91483             this.handle = this.el;
91484         }
91485
91486         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
91487         // We process mousedown to begin tracking.
91488         this.mon(this.handle, {
91489             mousedown: this.onMouseDown,
91490             delegate: this.delegate,
91491             scope: this
91492         });
91493
91494         // If configured to do so, track mouse entry and exit into the target (or delegate).
91495         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
91496         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
91497         if (this.trackOver || this.overCls) {
91498             this.mon(this.handle, {
91499                 mouseover: this.onMouseOver,
91500                 mouseout: this.onMouseOut,
91501                 delegate: this.delegate,
91502                 scope: this
91503             });
91504         }
91505     },
91506
91507     disable: function() {
91508         this.disabled = true;
91509     },
91510
91511     enable: function() {
91512         this.disabled = false;
91513     },
91514
91515     destroy : function() {
91516         this.clearListeners();
91517         delete this.el;
91518     },
91519
91520     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
91521     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
91522     onMouseOver: function(e, target) {
91523         var me = this;
91524         if (!me.disabled) {
91525             if (Ext.EventManager.contains(e) || me.delegate) {
91526                 me.mouseIsOut = false;
91527                 if (me.overCls) {
91528                     me.el.addCls(me.overCls);
91529                 }
91530                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
91531             }
91532         }
91533     },
91534
91535     // When the pointer exits a tracking element, fire a mouseout.
91536     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
91537     onMouseOut: function(e) {
91538         if (this.mouseIsDown) {
91539             this.mouseIsOut = true;
91540         } else {
91541             if (this.overCls) {
91542                 this.el.removeCls(this.overCls);
91543             }
91544             this.fireEvent('mouseout', this, e);
91545         }
91546     },
91547
91548     onMouseDown: function(e, target){
91549         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
91550         if (this.disabled ||e.dragTracked) {
91551             return;
91552         }
91553
91554         // This information should be available in mousedown listener and onBeforeStart implementations
91555         this.dragTarget = this.delegate ? target : this.handle.dom;
91556         this.startXY = this.lastXY = e.getXY();
91557         this.startRegion = Ext.fly(this.dragTarget).getRegion();
91558
91559         if (this.fireEvent('mousedown', this, e) === false ||
91560             this.fireEvent('beforedragstart', this, e) === false ||
91561             this.onBeforeStart(e) === false) {
91562             return;
91563         }
91564
91565         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
91566         // The onMouseOut method will only ever be called after mouseup.
91567         this.mouseIsDown = true;
91568
91569         // Flag for downstream DragTracker instances that the mouse is being tracked.
91570         e.dragTracked = true;
91571
91572         if (this.preventDefault !== false) {
91573             e.preventDefault();
91574         }
91575         Ext.getDoc().on({
91576             scope: this,
91577             mouseup: this.onMouseUp,
91578             mousemove: this.onMouseMove,
91579             selectstart: this.stopSelect
91580         });
91581         if (this.autoStart) {
91582             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
91583         }
91584     },
91585
91586     onMouseMove: function(e, target){
91587         // BrowserBug: IE hack to see if button was released outside of window.
91588         // Needed in IE6-9 in quirks and strictmode
91589         if (this.active && Ext.isIE && !e.browserEvent.button) {
91590             e.preventDefault();
91591             this.onMouseUp(e);
91592             return;
91593         }
91594
91595         e.preventDefault();
91596         var xy = e.getXY(),
91597             s = this.startXY;
91598
91599         this.lastXY = xy;
91600         if (!this.active) {
91601             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
91602                 this.triggerStart(e);
91603             } else {
91604                 return;
91605             }
91606         }
91607
91608         // Returning false from a mousemove listener deactivates
91609         if (this.fireEvent('mousemove', this, e) === false) {
91610             this.onMouseUp(e);
91611         } else {
91612             this.onDrag(e);
91613             this.fireEvent('drag', this, e);
91614         }
91615     },
91616
91617     onMouseUp: function(e) {
91618         // Clear the flag which ensures onMouseOut fires only after the mouse button
91619         // is lifted if the mouseout happens *during* a drag.
91620         this.mouseIsDown = false;
91621
91622         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
91623         if (this.mouseIsOut) {
91624             this.mouseIsOut = false;
91625             this.onMouseOut(e);
91626         }
91627         e.preventDefault();
91628         this.fireEvent('mouseup', this, e);
91629         this.endDrag(e);
91630     },
91631
91632     /**
91633      * @private
91634      * Stop the drag operation, and remove active mouse listeners.
91635      */
91636     endDrag: function(e) {
91637         var doc = Ext.getDoc(),
91638         wasActive = this.active;
91639
91640         doc.un('mousemove', this.onMouseMove, this);
91641         doc.un('mouseup', this.onMouseUp, this);
91642         doc.un('selectstart', this.stopSelect, this);
91643         this.clearStart();
91644         this.active = false;
91645         if (wasActive) {
91646             this.onEnd(e);
91647             this.fireEvent('dragend', this, e);
91648         }
91649         // Private property calculated when first required and only cached during a drag
91650         delete this._constrainRegion;
91651
91652         // Remove flag from event singleton.  Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
91653         delete Ext.EventObject.dragTracked;
91654     },
91655
91656     triggerStart: function(e) {
91657         this.clearStart();
91658         this.active = true;
91659         this.onStart(e);
91660         this.fireEvent('dragstart', this, e);
91661     },
91662
91663     clearStart : function() {
91664         if (this.timer) {
91665             clearTimeout(this.timer);
91666             delete this.timer;
91667         }
91668     },
91669
91670     stopSelect : function(e) {
91671         e.stopEvent();
91672         return false;
91673     },
91674
91675     /**
91676      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
91677      * holds the mouse button down. Return false to disallow the drag
91678      * @param {Ext.EventObject} e The event object
91679      * @template
91680      */
91681     onBeforeStart : function(e) {
91682
91683     },
91684
91685     /**
91686      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
91687      * (e.g. the user has moved the tracked element beyond the specified tolerance)
91688      * @param {Ext.EventObject} e The event object
91689      * @template
91690      */
91691     onStart : function(xy) {
91692
91693     },
91694
91695     /**
91696      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
91697      * @param {Ext.EventObject} e The event object
91698      * @template
91699      */
91700     onDrag : function(e) {
91701
91702     },
91703
91704     /**
91705      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
91706      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
91707      * @param {Ext.EventObject} e The event object
91708      * @template
91709      */
91710     onEnd : function(e) {
91711
91712     },
91713
91714     /**
91715      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
91716      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
91717      * {@link #delegate} selector.</p>
91718      * @return {Ext.Element} The element currently being tracked.
91719      */
91720     getDragTarget : function(){
91721         return this.dragTarget;
91722     },
91723
91724     /**
91725      * @private
91726      * @returns {Ext.Element} The DragTracker's encapsulating element.
91727      */
91728     getDragCt : function(){
91729         return this.el;
91730     },
91731
91732     /**
91733      * @private
91734      * Return the Region into which the drag operation is constrained.
91735      * Either the XY pointer itself can be constrained, or the dragTarget element
91736      * The private property _constrainRegion is cached until onMouseUp
91737      */
91738     getConstrainRegion: function() {
91739         if (this.constrainTo) {
91740             if (this.constrainTo instanceof Ext.util.Region) {
91741                 return this.constrainTo;
91742             }
91743             if (!this._constrainRegion) {
91744                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
91745             }
91746         } else {
91747             if (!this._constrainRegion) {
91748                 this._constrainRegion = this.getDragCt().getViewRegion();
91749             }
91750         }
91751         return this._constrainRegion;
91752     },
91753
91754     getXY : function(constrain){
91755         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
91756     },
91757
91758     /**
91759      * Returns the X, Y offset of the current mouse position from the mousedown point.
91760      *
91761      * This method may optionally constrain the real offset values, and returns a point coerced in one
91762      * of two modes:
91763      *
91764      *  - `point`
91765      *    The current mouse position is coerced into the constrainRegion and the resulting position is returned.
91766      *  - `dragTarget`
91767      *    The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
91768      *    based upon the current mouse position, and then coerced into the constrainRegion. The returned
91769      *    mouse position is then adjusted by the same delta as was used to coerce the region.\
91770      *
91771      * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
91772      * as `point` or `dragTarget`. See above.
91773      * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
91774      */
91775     getOffset : function(constrain){
91776         var xy = this.getXY(constrain),
91777             s = this.startXY;
91778
91779         return [xy[0]-s[0], xy[1]-s[1]];
91780     },
91781
91782     constrainModes: {
91783         // Constrain the passed point to within the constrain region
91784         point: function(me, xy) {
91785             var dr = me.dragRegion,
91786                 constrainTo = me.getConstrainRegion();
91787
91788             // No constraint
91789             if (!constrainTo) {
91790                 return xy;
91791             }
91792
91793             dr.x = dr.left = dr[0] = dr.right = xy[0];
91794             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
91795             dr.constrainTo(constrainTo);
91796
91797             return [dr.left, dr.top];
91798         },
91799
91800         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
91801         dragTarget: function(me, xy) {
91802             var s = me.startXY,
91803                 dr = me.startRegion.copy(),
91804                 constrainTo = me.getConstrainRegion(),
91805                 adjust;
91806
91807             // No constraint
91808             if (!constrainTo) {
91809                 return xy;
91810             }
91811
91812             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
91813             // If it overflows, we constrain the passed XY to bring the potential
91814             // region back within the boundary.
91815             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
91816
91817             // Constrain the X coordinate by however much the dragTarget overflows
91818             if (dr.right > constrainTo.right) {
91819                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
91820                 dr.left += adjust;
91821             }
91822             if (dr.left < constrainTo.left) {
91823                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
91824             }
91825
91826             // Constrain the Y coordinate by however much the dragTarget overflows
91827             if (dr.bottom > constrainTo.bottom) {
91828                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
91829                 dr.top += adjust;
91830             }
91831             if (dr.top < constrainTo.top) {
91832                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
91833             }
91834             return xy;
91835         }
91836     }
91837 });
91838 /**
91839  * @class Ext.dd.DragZone
91840  * @extends Ext.dd.DragSource
91841  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
91842  * <p>This class does not move the drag target nodes, but a proxy element which may contain
91843  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
91844  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
91845  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
91846  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
91847  * is the most efficient way to "activate" those nodes.</p>
91848  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
91849  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
91850  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
91851  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
91852  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91853  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
91854  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
91855 myDataView.on('render', function(v) {
91856     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
91857
91858 //      On receipt of a mousedown event, see if it is within a DataView node.
91859 //      Return a drag data object if so.
91860         getDragData: function(e) {
91861
91862 //          Use the DataView's own itemSelector (a mandatory property) to
91863 //          test if the mousedown is within one of the DataView's nodes.
91864             var sourceEl = e.getTarget(v.itemSelector, 10);
91865
91866 //          If the mousedown is within a DataView node, clone the node to produce
91867 //          a ddel element for use by the drag proxy. Also add application data
91868 //          to the returned data object.
91869             if (sourceEl) {
91870                 d = sourceEl.cloneNode(true);
91871                 d.id = Ext.id();
91872                 return {
91873                     ddel: d,
91874                     sourceEl: sourceEl,
91875                     repairXY: Ext.fly(sourceEl).getXY(),
91876                     sourceStore: v.store,
91877                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
91878                 }
91879             }
91880         },
91881
91882 //      Provide coordinates for the proxy to slide back to on failed drag.
91883 //      This is the original XY coordinates of the draggable element captured
91884 //      in the getDragData method.
91885         getRepairXY: function() {
91886             return this.dragData.repairXY;
91887         }
91888     });
91889 });</code></pre>
91890  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
91891  * cooperates with this DragZone.
91892  */
91893 Ext.define('Ext.dd.DragZone', {
91894
91895     extend: 'Ext.dd.DragSource',
91896
91897     /**
91898      * Creates new DragZone.
91899      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91900      * @param {Object} config
91901      */
91902     constructor : function(el, config){
91903         this.callParent([el, config]);
91904         if (this.containerScroll) {
91905             Ext.dd.ScrollManager.register(this.el);
91906         }
91907     },
91908
91909     /**
91910      * This property contains the data representing the dragged object. This data is set up by the implementation
91911      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
91912      * any other data according to the application's needs.
91913      * @type Object
91914      * @property dragData
91915      */
91916
91917     /**
91918      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
91919      * for auto scrolling during drag operations.
91920      */
91921
91922     /**
91923      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
91924      * for a valid target to drag based on the mouse down. Override this method
91925      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
91926      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
91927      * @param {Event} e The mouse down event
91928      * @return {Object} The dragData
91929      */
91930     getDragData : function(e){
91931         return Ext.dd.Registry.getHandleFromEvent(e);
91932     },
91933
91934     /**
91935      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
91936      * this.dragData.ddel
91937      * @param {Number} x The x position of the click on the dragged object
91938      * @param {Number} y The y position of the click on the dragged object
91939      * @return {Boolean} true to continue the drag, false to cancel
91940      */
91941     onInitDrag : function(x, y){
91942         this.proxy.update(this.dragData.ddel.cloneNode(true));
91943         this.onStartDrag(x, y);
91944         return true;
91945     },
91946
91947     /**
91948      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
91949      */
91950     afterRepair : function(){
91951         var me = this;
91952         if (Ext.enableFx) {
91953             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
91954         }
91955         me.dragging = false;
91956     },
91957
91958     /**
91959      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
91960      * the XY of this.dragData.ddel
91961      * @param {Event} e The mouse up event
91962      * @return {Number[]} The xy location (e.g. [100, 200])
91963      */
91964     getRepairXY : function(e){
91965         return Ext.Element.fly(this.dragData.ddel).getXY();
91966     },
91967
91968     destroy : function(){
91969         this.callParent();
91970         if (this.containerScroll) {
91971             Ext.dd.ScrollManager.unregister(this.el);
91972         }
91973     }
91974 });
91975
91976 /**
91977  * @class Ext.dd.ScrollManager
91978  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
91979  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
91980  * but you can also override most of the configs per scroll container by adding a
91981  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
91982  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
91983  * <pre><code>
91984 var el = Ext.get('scroll-ct');
91985 el.ddScrollConfig = {
91986     vthresh: 50,
91987     hthresh: -1,
91988     frequency: 100,
91989     increment: 200
91990 };
91991 Ext.dd.ScrollManager.register(el);
91992 </code></pre>
91993  * Note: This class is designed to be used in "Point Mode
91994  * @singleton
91995  */
91996 Ext.define('Ext.dd.ScrollManager', {
91997     singleton: true,
91998     requires: [
91999         'Ext.dd.DragDropManager'
92000     ],
92001
92002     constructor: function() {
92003         var ddm = Ext.dd.DragDropManager;
92004         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
92005         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
92006         this.doScroll = Ext.Function.bind(this.doScroll, this);
92007         this.ddmInstance = ddm;
92008         this.els = {};
92009         this.dragEl = null;
92010         this.proc = {};
92011     },
92012
92013     onStop: function(e){
92014         var sm = Ext.dd.ScrollManager;
92015         sm.dragEl = null;
92016         sm.clearProc();
92017     },
92018
92019     triggerRefresh: function() {
92020         if (this.ddmInstance.dragCurrent) {
92021             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
92022         }
92023     },
92024
92025     doScroll: function() {
92026         if (this.ddmInstance.dragCurrent) {
92027             var proc   = this.proc,
92028                 procEl = proc.el,
92029                 ddScrollConfig = proc.el.ddScrollConfig,
92030                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
92031
92032             if (!this.animate) {
92033                 if (procEl.scroll(proc.dir, inc)) {
92034                     this.triggerRefresh();
92035                 }
92036             } else {
92037                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
92038             }
92039         }
92040     },
92041
92042     clearProc: function() {
92043         var proc = this.proc;
92044         if (proc.id) {
92045             clearInterval(proc.id);
92046         }
92047         proc.id = 0;
92048         proc.el = null;
92049         proc.dir = "";
92050     },
92051
92052     startProc: function(el, dir) {
92053         this.clearProc();
92054         this.proc.el = el;
92055         this.proc.dir = dir;
92056         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
92057             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
92058                   ? el.ddScrollConfig.frequency
92059                   : this.frequency;
92060
92061         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
92062             this.proc.id = setInterval(this.doScroll, freq);
92063         }
92064     },
92065
92066     onFire: function(e, isDrop) {
92067         if (isDrop || !this.ddmInstance.dragCurrent) {
92068             return;
92069         }
92070         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
92071             this.dragEl = this.ddmInstance.dragCurrent;
92072             // refresh regions on drag start
92073             this.refreshCache();
92074         }
92075
92076         var xy = e.getXY(),
92077             pt = e.getPoint(),
92078             proc = this.proc,
92079             els = this.els;
92080
92081         for (var id in els) {
92082             var el = els[id], r = el._region;
92083             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
92084             if (r && r.contains(pt) && el.isScrollable()) {
92085                 if (r.bottom - pt.y <= c.vthresh) {
92086                     if(proc.el != el){
92087                         this.startProc(el, "down");
92088                     }
92089                     return;
92090                 }else if (r.right - pt.x <= c.hthresh) {
92091                     if (proc.el != el) {
92092                         this.startProc(el, "left");
92093                     }
92094                     return;
92095                 } else if(pt.y - r.top <= c.vthresh) {
92096                     if (proc.el != el) {
92097                         this.startProc(el, "up");
92098                     }
92099                     return;
92100                 } else if(pt.x - r.left <= c.hthresh) {
92101                     if (proc.el != el) {
92102                         this.startProc(el, "right");
92103                     }
92104                     return;
92105                 }
92106             }
92107         }
92108         this.clearProc();
92109     },
92110
92111     /**
92112      * Registers new overflow element(s) to auto scroll
92113      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
92114      * The id of or the element to be scrolled or an array of either
92115      */
92116     register : function(el){
92117         if (Ext.isArray(el)) {
92118             for(var i = 0, len = el.length; i < len; i++) {
92119                     this.register(el[i]);
92120             }
92121         } else {
92122             el = Ext.get(el);
92123             this.els[el.id] = el;
92124         }
92125     },
92126
92127     /**
92128      * Unregisters overflow element(s) so they are no longer scrolled
92129      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
92130      * The id of or the element to be removed or an array of either
92131      */
92132     unregister : function(el){
92133         if(Ext.isArray(el)){
92134             for (var i = 0, len = el.length; i < len; i++) {
92135                 this.unregister(el[i]);
92136             }
92137         }else{
92138             el = Ext.get(el);
92139             delete this.els[el.id];
92140         }
92141     },
92142
92143     /**
92144      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
92145      * trigger scrolling
92146      * @type Number
92147      */
92148     vthresh : 25,
92149     /**
92150      * The number of pixels from the right or left edge of a container the pointer needs to be to
92151      * trigger scrolling
92152      * @type Number
92153      */
92154     hthresh : 25,
92155
92156     /**
92157      * The number of pixels to scroll in each scroll increment
92158      * @type Number
92159      */
92160     increment : 100,
92161
92162     /**
92163      * The frequency of scrolls in milliseconds
92164      * @type Number
92165      */
92166     frequency : 500,
92167
92168     /**
92169      * True to animate the scroll
92170      * @type Boolean
92171      */
92172     animate: true,
92173
92174     /**
92175      * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
92176      * @type Number
92177      */
92178     animDuration: 0.4,
92179
92180     /**
92181      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs.
92182      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
92183      * @type String
92184      */
92185     ddGroup: undefined,
92186
92187     /**
92188      * Manually trigger a cache refresh.
92189      */
92190     refreshCache : function(){
92191         var els = this.els,
92192             id;
92193         for (id in els) {
92194             if(typeof els[id] == 'object'){ // for people extending the object prototype
92195                 els[id]._region = els[id].getRegion();
92196             }
92197         }
92198     }
92199 });
92200
92201 /**
92202  * @class Ext.dd.DropTarget
92203  * @extends Ext.dd.DDTarget
92204  * A simple class that provides the basic implementation needed to make any element a drop target that can have
92205  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
92206  */
92207 Ext.define('Ext.dd.DropTarget', {
92208     extend: 'Ext.dd.DDTarget',
92209     requires: ['Ext.dd.ScrollManager'],
92210
92211     /**
92212      * Creates new DropTarget.
92213      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
92214      * @param {Object} config
92215      */
92216     constructor : function(el, config){
92217         this.el = Ext.get(el);
92218
92219         Ext.apply(this, config);
92220
92221         if(this.containerScroll){
92222             Ext.dd.ScrollManager.register(this.el);
92223         }
92224
92225         this.callParent([this.el.dom, this.ddGroup || this.group,
92226               {isTarget: true}]);
92227     },
92228
92229     /**
92230      * @cfg {String} ddGroup
92231      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
92232      * interact with other drag drop objects in the same group.
92233      */
92234     /**
92235      * @cfg {String} [overClass=""]
92236      * The CSS class applied to the drop target element while the drag source is over it.
92237      */
92238     /**
92239      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
92240      * The CSS class returned to the drag source when drop is allowed.
92241      */
92242     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
92243     /**
92244      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
92245      * The CSS class returned to the drag source when drop is not allowed.
92246      */
92247     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
92248
92249     // private
92250     isTarget : true,
92251
92252     // private
92253     isNotifyTarget : true,
92254
92255     /**
92256      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
92257      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
92258      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
92259      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92260      * @param {Event} e The event
92261      * @param {Object} data An object containing arbitrary data supplied by the drag source
92262      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92263      * underlying {@link Ext.dd.StatusProxy} can be updated
92264      */
92265     notifyEnter : function(dd, e, data){
92266         if(this.overClass){
92267             this.el.addCls(this.overClass);
92268         }
92269         return this.dropAllowed;
92270     },
92271
92272     /**
92273      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
92274      * This method will be called on every mouse movement while the drag source is over the drop target.
92275      * This default implementation simply returns the dropAllowed config value.
92276      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92277      * @param {Event} e The event
92278      * @param {Object} data An object containing arbitrary data supplied by the drag source
92279      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92280      * underlying {@link Ext.dd.StatusProxy} can be updated
92281      */
92282     notifyOver : function(dd, e, data){
92283         return this.dropAllowed;
92284     },
92285
92286     /**
92287      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
92288      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
92289      * overClass (if any) from the drop element.
92290      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92291      * @param {Event} e The event
92292      * @param {Object} data An object containing arbitrary data supplied by the drag source
92293      */
92294     notifyOut : function(dd, e, data){
92295         if(this.overClass){
92296             this.el.removeCls(this.overClass);
92297         }
92298     },
92299
92300     /**
92301      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
92302      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
92303      * implementation that does something to process the drop event and returns true so that the drag source's
92304      * repair action does not run.
92305      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92306      * @param {Event} e The event
92307      * @param {Object} data An object containing arbitrary data supplied by the drag source
92308      * @return {Boolean} False if the drop was invalid.
92309      */
92310     notifyDrop : function(dd, e, data){
92311         return false;
92312     },
92313
92314     destroy : function(){
92315         this.callParent();
92316         if(this.containerScroll){
92317             Ext.dd.ScrollManager.unregister(this.el);
92318         }
92319     }
92320 });
92321
92322 /**
92323  * @class Ext.dd.Registry
92324  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
92325  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
92326  * @singleton
92327  */
92328 Ext.define('Ext.dd.Registry', {
92329     singleton: true,
92330     constructor: function() {
92331         this.elements = {}; 
92332         this.handles = {}; 
92333         this.autoIdSeed = 0;
92334     },
92335     
92336     getId: function(el, autogen){
92337         if(typeof el == "string"){
92338             return el;
92339         }
92340         var id = el.id;
92341         if(!id && autogen !== false){
92342             id = "extdd-" + (++this.autoIdSeed);
92343             el.id = id;
92344         }
92345         return id;
92346     },
92347     
92348     /**
92349      * Resgister a drag drop element
92350      * @param {String/HTMLElement} element The id or DOM node to register
92351      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
92352      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
92353      * knows how to interpret, plus there are some specific properties known to the Registry that should be
92354      * populated in the data object (if applicable):
92355      * <pre>
92356 Value      Description<br />
92357 ---------  ------------------------------------------<br />
92358 handles    Array of DOM nodes that trigger dragging<br />
92359            for the element being registered<br />
92360 isHandle   True if the element passed in triggers<br />
92361            dragging itself, else false
92362 </pre>
92363      */
92364     register : function(el, data){
92365         data = data || {};
92366         if (typeof el == "string") {
92367             el = document.getElementById(el);
92368         }
92369         data.ddel = el;
92370         this.elements[this.getId(el)] = data;
92371         if (data.isHandle !== false) {
92372             this.handles[data.ddel.id] = data;
92373         }
92374         if (data.handles) {
92375             var hs = data.handles;
92376             for (var i = 0, len = hs.length; i < len; i++) {
92377                 this.handles[this.getId(hs[i])] = data;
92378             }
92379         }
92380     },
92381
92382     /**
92383      * Unregister a drag drop element
92384      * @param {String/HTMLElement} element The id or DOM node to unregister
92385      */
92386     unregister : function(el){
92387         var id = this.getId(el, false);
92388         var data = this.elements[id];
92389         if(data){
92390             delete this.elements[id];
92391             if(data.handles){
92392                 var hs = data.handles;
92393                 for (var i = 0, len = hs.length; i < len; i++) {
92394                     delete this.handles[this.getId(hs[i], false)];
92395                 }
92396             }
92397         }
92398     },
92399
92400     /**
92401      * Returns the handle registered for a DOM Node by id
92402      * @param {String/HTMLElement} id The DOM node or id to look up
92403      * @return {Object} handle The custom handle data
92404      */
92405     getHandle : function(id){
92406         if(typeof id != "string"){ // must be element?
92407             id = id.id;
92408         }
92409         return this.handles[id];
92410     },
92411
92412     /**
92413      * Returns the handle that is registered for the DOM node that is the target of the event
92414      * @param {Event} e The event
92415      * @return {Object} handle The custom handle data
92416      */
92417     getHandleFromEvent : function(e){
92418         var t = e.getTarget();
92419         return t ? this.handles[t.id] : null;
92420     },
92421
92422     /**
92423      * Returns a custom data object that is registered for a DOM node by id
92424      * @param {String/HTMLElement} id The DOM node or id to look up
92425      * @return {Object} data The custom data
92426      */
92427     getTarget : function(id){
92428         if(typeof id != "string"){ // must be element?
92429             id = id.id;
92430         }
92431         return this.elements[id];
92432     },
92433
92434     /**
92435      * Returns a custom data object that is registered for the DOM node that is the target of the event
92436      * @param {Event} e The event
92437      * @return {Object} data The custom data
92438      */
92439     getTargetFromEvent : function(e){
92440         var t = e.getTarget();
92441         return t ? this.elements[t.id] || this.handles[t.id] : null;
92442     }
92443 });
92444 /**
92445  * @class Ext.dd.DropZone
92446  * @extends Ext.dd.DropTarget
92447
92448 This class provides a container DD instance that allows dropping on multiple child target nodes.
92449
92450 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
92451 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
92452 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
92453 mouse event to see if it has taken place within an element, or class of elements. This is easily done
92454 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
92455 {@link Ext.DomQuery} selector.
92456
92457 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
92458 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
92459 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
92460 of these methods to provide application-specific behaviour for these events to update both
92461 application state, and UI state.
92462
92463 For example to make a GridPanel a cooperating target with the example illustrated in
92464 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
92465
92466     myGridPanel.on('render', function() {
92467         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
92468
92469             // If the mouse is over a grid row, return that node. This is
92470             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
92471             getTargetFromEvent: function(e) {
92472                 return e.getTarget(myGridPanel.getView().rowSelector);
92473             },
92474
92475             // On entry into a target node, highlight that node.
92476             onNodeEnter : function(target, dd, e, data){ 
92477                 Ext.fly(target).addCls('my-row-highlight-class');
92478             },
92479
92480             // On exit from a target node, unhighlight that node.
92481             onNodeOut : function(target, dd, e, data){ 
92482                 Ext.fly(target).removeCls('my-row-highlight-class');
92483             },
92484
92485             // While over a target node, return the default drop allowed class which
92486             // places a "tick" icon into the drag proxy.
92487             onNodeOver : function(target, dd, e, data){ 
92488                 return Ext.dd.DropZone.prototype.dropAllowed;
92489             },
92490
92491             // On node drop we can interrogate the target to find the underlying
92492             // application object that is the real target of the dragged data.
92493             // In this case, it is a Record in the GridPanel's Store.
92494             // We can use the data set up by the DragZone's getDragData method to read
92495             // any data we decided to attach in the DragZone's getDragData method.
92496             onNodeDrop : function(target, dd, e, data){
92497                 var rowIndex = myGridPanel.getView().findRowIndex(target);
92498                 var r = myGridPanel.getStore().getAt(rowIndex);
92499                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
92500                     ' on Record id ' + r.id);
92501                 return true;
92502             }
92503         });
92504     }
92505
92506 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
92507 cooperates with this DropZone.
92508
92509  * @markdown
92510  */
92511 Ext.define('Ext.dd.DropZone', {
92512     extend: 'Ext.dd.DropTarget',
92513     requires: ['Ext.dd.Registry'],
92514
92515     /**
92516      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
92517      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
92518      * provide your own custom lookup.
92519      * @param {Event} e The event
92520      * @return {Object} data The custom data
92521      */
92522     getTargetFromEvent : function(e){
92523         return Ext.dd.Registry.getTargetFromEvent(e);
92524     },
92525
92526     /**
92527      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
92528      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
92529      * This method has no default implementation and should be overridden to provide
92530      * node-specific processing if necessary.
92531      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
92532      * {@link #getTargetFromEvent} for this node)
92533      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92534      * @param {Event} e The event
92535      * @param {Object} data An object containing arbitrary data supplied by the drag source
92536      */
92537     onNodeEnter : function(n, dd, e, data){
92538         
92539     },
92540
92541     /**
92542      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
92543      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
92544      * The default implementation returns this.dropNotAllowed, so it should be
92545      * overridden to provide the proper feedback.
92546      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
92547      * {@link #getTargetFromEvent} for this node)
92548      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92549      * @param {Event} e The event
92550      * @param {Object} data An object containing arbitrary data supplied by the drag source
92551      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92552      * underlying {@link Ext.dd.StatusProxy} can be updated
92553      */
92554     onNodeOver : function(n, dd, e, data){
92555         return this.dropAllowed;
92556     },
92557
92558     /**
92559      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
92560      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
92561      * node-specific processing if necessary.
92562      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
92563      * {@link #getTargetFromEvent} for this node)
92564      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92565      * @param {Event} e The event
92566      * @param {Object} data An object containing arbitrary data supplied by the drag source
92567      */
92568     onNodeOut : function(n, dd, e, data){
92569         
92570     },
92571
92572     /**
92573      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
92574      * the drop node.  The default implementation returns false, so it should be overridden to provide the
92575      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
92576      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
92577      * {@link #getTargetFromEvent} for this node)
92578      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92579      * @param {Event} e The event
92580      * @param {Object} data An object containing arbitrary data supplied by the drag source
92581      * @return {Boolean} True if the drop was valid, else false
92582      */
92583     onNodeDrop : function(n, dd, e, data){
92584         return false;
92585     },
92586
92587     /**
92588      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
92589      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
92590      * it should be overridden to provide the proper feedback if necessary.
92591      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92592      * @param {Event} e The event
92593      * @param {Object} data An object containing arbitrary data supplied by the drag source
92594      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92595      * underlying {@link Ext.dd.StatusProxy} can be updated
92596      */
92597     onContainerOver : function(dd, e, data){
92598         return this.dropNotAllowed;
92599     },
92600
92601     /**
92602      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
92603      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
92604      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
92605      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
92606      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92607      * @param {Event} e The event
92608      * @param {Object} data An object containing arbitrary data supplied by the drag source
92609      * @return {Boolean} True if the drop was valid, else false
92610      */
92611     onContainerDrop : function(dd, e, data){
92612         return false;
92613     },
92614
92615     /**
92616      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
92617      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
92618      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
92619      * you should override this method and provide a custom implementation.
92620      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92621      * @param {Event} e The event
92622      * @param {Object} data An object containing arbitrary data supplied by the drag source
92623      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92624      * underlying {@link Ext.dd.StatusProxy} can be updated
92625      */
92626     notifyEnter : function(dd, e, data){
92627         return this.dropNotAllowed;
92628     },
92629
92630     /**
92631      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
92632      * This method will be called on every mouse movement while the drag source is over the drop zone.
92633      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
92634      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
92635      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
92636      * registered node, it will call {@link #onContainerOver}.
92637      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92638      * @param {Event} e The event
92639      * @param {Object} data An object containing arbitrary data supplied by the drag source
92640      * @return {String} status The CSS class that communicates the drop status back to the source so that the
92641      * underlying {@link Ext.dd.StatusProxy} can be updated
92642      */
92643     notifyOver : function(dd, e, data){
92644         var n = this.getTargetFromEvent(e);
92645         if(!n) { // not over valid drop target
92646             if(this.lastOverNode){
92647                 this.onNodeOut(this.lastOverNode, dd, e, data);
92648                 this.lastOverNode = null;
92649             }
92650             return this.onContainerOver(dd, e, data);
92651         }
92652         if(this.lastOverNode != n){
92653             if(this.lastOverNode){
92654                 this.onNodeOut(this.lastOverNode, dd, e, data);
92655             }
92656             this.onNodeEnter(n, dd, e, data);
92657             this.lastOverNode = n;
92658         }
92659         return this.onNodeOver(n, dd, e, data);
92660     },
92661
92662     /**
92663      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
92664      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
92665      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
92666      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
92667      * @param {Event} e The event
92668      * @param {Object} data An object containing arbitrary data supplied by the drag zone
92669      */
92670     notifyOut : function(dd, e, data){
92671         if(this.lastOverNode){
92672             this.onNodeOut(this.lastOverNode, dd, e, data);
92673             this.lastOverNode = null;
92674         }
92675     },
92676
92677     /**
92678      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
92679      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
92680      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
92681      * otherwise it will call {@link #onContainerDrop}.
92682      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
92683      * @param {Event} e The event
92684      * @param {Object} data An object containing arbitrary data supplied by the drag source
92685      * @return {Boolean} False if the drop was invalid.
92686      */
92687     notifyDrop : function(dd, e, data){
92688         if(this.lastOverNode){
92689             this.onNodeOut(this.lastOverNode, dd, e, data);
92690             this.lastOverNode = null;
92691         }
92692         var n = this.getTargetFromEvent(e);
92693         return n ?
92694             this.onNodeDrop(n, dd, e, data) :
92695             this.onContainerDrop(dd, e, data);
92696     },
92697
92698     // private
92699     triggerCacheRefresh : function() {
92700         Ext.dd.DDM.refreshCache(this.groups);
92701     }
92702 });
92703 /**
92704  * @class Ext.flash.Component
92705  * @extends Ext.Component
92706  *
92707  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
92708  * in layout like any other Component.
92709  *
92710  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
92711  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
92712  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
92713  * and then simply import it into the head of your HTML document:
92714  *
92715  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
92716  *
92717  * ## Configuration
92718  *
92719  * This component allows several options for configuring how the target Flash movie is embedded. The most
92720  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
92721  * configurations include:
92722  *
92723  * - {@link #backgroundColor}
92724  * - {@link #wmode}
92725  * - {@link #flashVars}
92726  * - {@link #flashParams}
92727  * - {@link #flashAttributes}
92728  *
92729  * ## Example usage:
92730  *
92731  *     var win = Ext.widget('window', {
92732  *         title: "It's a tiger!",
92733  *         layout: 'fit',
92734  *         width: 300,
92735  *         height: 300,
92736  *         x: 20,
92737  *         y: 20,
92738  *         resizable: true,
92739  *         items: {
92740  *             xtype: 'flash',
92741  *             url: 'tiger.swf'
92742  *         }
92743  *     });
92744  *     win.show();
92745  *
92746  * ## Express Install
92747  *
92748  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
92749  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
92750  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
92751  *
92752  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
92753  *
92754  * @docauthor Jason Johnston <jason@sencha.com>
92755  */
92756 Ext.define('Ext.flash.Component', {
92757     extend: 'Ext.Component',
92758     alternateClassName: 'Ext.FlashComponent',
92759     alias: 'widget.flash',
92760
92761     /**
92762      * @cfg {String} flashVersion
92763      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
92764      */
92765     flashVersion : '9.0.115',
92766
92767     /**
92768      * @cfg {String} backgroundColor
92769      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
92770      */
92771     backgroundColor: '#ffffff',
92772
92773     /**
92774      * @cfg {String} wmode
92775      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
92776      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
92777      * movie transparent.
92778      */
92779     wmode: 'opaque',
92780
92781     /**
92782      * @cfg {Object} flashVars
92783      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
92784      */
92785
92786     /**
92787      * @cfg {Object} flashParams
92788      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
92789      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
92790      */
92791
92792     /**
92793      * @cfg {Object} flashAttributes
92794      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
92795      */
92796
92797     /**
92798      * @cfg {String} url
92799      * The URL of the SWF file to include. Required.
92800      */
92801
92802     /**
92803      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
92804      * so that the movie matches the width of the component.
92805      */
92806     swfWidth: '100%',
92807
92808     /**
92809      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
92810      * so that the movie matches the height of the component.
92811      */
92812     swfHeight: '100%',
92813
92814     /**
92815      * @cfg {Boolean} expressInstall
92816      * True to prompt the user to install flash if not installed. Note that this uses
92817      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
92818      */
92819     expressInstall: false,
92820
92821     /**
92822      * @property swf
92823      * @type {Ext.Element}
92824      * A reference to the object or embed element into which the SWF file is loaded. Only
92825      * populated after the component is rendered and the SWF has been successfully embedded.
92826      */
92827
92828     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
92829     renderTpl: ['<div id="{swfId}"></div>'],
92830
92831     initComponent: function() {
92832         if (!('swfobject' in window)) {
92833             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/');
92834         }
92835         if (!this.url) {
92836             Ext.Error.raise('The "url" config is required for Ext.flash.Component');
92837         }
92838
92839         this.callParent();
92840         this.addEvents(
92841             /**
92842              * @event success
92843              * Fired when the Flash movie has been successfully embedded
92844              * @param {Ext.flash.Component} this
92845              */
92846             'success',
92847
92848             /**
92849              * @event failure
92850              * Fired when the Flash movie embedding fails
92851              * @param {Ext.flash.Component} this
92852              */
92853             'failure'
92854         );
92855     },
92856
92857     onRender: function() {
92858         var me = this,
92859             params, vars, undef,
92860             swfId = me.getSwfId();
92861
92862         me.renderData.swfId = swfId;
92863
92864         me.callParent(arguments);
92865
92866         params = Ext.apply({
92867             allowScriptAccess: 'always',
92868             bgcolor: me.backgroundColor,
92869             wmode: me.wmode
92870         }, me.flashParams);
92871
92872         vars = Ext.apply({
92873             allowedDomain: document.location.hostname
92874         }, me.flashVars);
92875
92876         new swfobject.embedSWF(
92877             me.url,
92878             swfId,
92879             me.swfWidth,
92880             me.swfHeight,
92881             me.flashVersion,
92882             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
92883             vars,
92884             params,
92885             me.flashAttributes,
92886             Ext.bind(me.swfCallback, me)
92887         );
92888     },
92889
92890     /**
92891      * @private
92892      * The callback method for handling an embedding success or failure by SWFObject
92893      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
92894      */
92895     swfCallback: function(e) {
92896         var me = this;
92897         if (e.success) {
92898             me.swf = Ext.get(e.ref);
92899             me.onSuccess();
92900             me.fireEvent('success', me);
92901         } else {
92902             me.onFailure();
92903             me.fireEvent('failure', me);
92904         }
92905     },
92906
92907     /**
92908      * Retrieve the id of the SWF object/embed element
92909      */
92910     getSwfId: function() {
92911         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
92912     },
92913
92914     onSuccess: function() {
92915         // swfobject forces visiblity:visible on the swf element, which prevents it 
92916         // from getting hidden when an ancestor is given visibility:hidden.
92917         this.swf.setStyle('visibility', 'inherit');
92918     },
92919
92920     onFailure: Ext.emptyFn,
92921
92922     beforeDestroy: function() {
92923         var me = this,
92924             swf = me.swf;
92925         if (swf) {
92926             swfobject.removeSWF(me.getSwfId());
92927             Ext.destroy(swf);
92928             delete me.swf;
92929         }
92930         me.callParent();
92931     },
92932
92933     statics: {
92934         /**
92935          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
92936          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
92937          * @static
92938          * @type String
92939          */
92940         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
92941     }
92942 });
92943
92944 /**
92945  * @class Ext.form.action.Action
92946  * @extends Ext.Base
92947  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
92948  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92949  * the Form needs to perform an action such as submit or load. The Configuration options
92950  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
92951  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
92952  * <p>The instance of Action which performed the action is passed to the success
92953  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
92954  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
92955  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
92956  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
92957  */
92958 Ext.define('Ext.form.action.Action', {
92959     alternateClassName: 'Ext.form.Action',
92960
92961     /**
92962      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
92963      * is invoking this Action. Required.
92964      */
92965
92966     /**
92967      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
92968      * configured on the {@link #form}.
92969      */
92970
92971     /**
92972      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
92973      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
92974      * before the {@link #success} callback is called and before the Form's
92975      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
92976      */
92977
92978     /**
92979      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
92980      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
92981      */
92982
92983     /**
92984      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
92985      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
92986      * input fields.</p>
92987      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
92988      */
92989
92990     /**
92991      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
92992      * {@link Ext.data.proxy.Ajax#headers}.</p>
92993      */
92994
92995     /**
92996      * @cfg {Number} timeout The number of seconds to wait for a server response before
92997      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
92998      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
92999      * {@link #form}.
93000      */
93001
93002     /**
93003      * @cfg {Function} success The function to call when a valid success return packet is received.
93004      * The function is passed the following parameters:<ul class="mdetail-params">
93005      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
93006      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
93007      * property of this object may be examined to perform custom postprocessing.</div></li>
93008      * </ul>
93009      */
93010
93011     /**
93012      * @cfg {Function} failure The function to call when a failure packet was received, or when an
93013      * error ocurred in the Ajax communication.
93014      * The function is passed the following parameters:<ul class="mdetail-params">
93015      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
93016      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
93017      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
93018      * property of this object may be examined to perform custom postprocessing.</div></li>
93019      * </ul>
93020      */
93021
93022     /**
93023      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
93024      * callback functions (the <tt>this</tt> reference for the callback functions).
93025      */
93026
93027     /**
93028      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
93029      * during the time the action is being processed.
93030      */
93031
93032     /**
93033      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
93034      * during the time the action is being processed.
93035      */
93036
93037     /**
93038      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
93039      * when it is submitted. Defaults to <tt>true</tt>.
93040      */
93041     submitEmptyText : true,
93042     /**
93043      * @property type
93044      * The type of action this Action instance performs.
93045      * Currently only "submit" and "load" are supported.
93046      * @type {String}
93047      */
93048
93049     /**
93050      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
93051      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
93052      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
93053      * <pre><code>
93054 var fp = new Ext.form.Panel({
93055 ...
93056 buttons: [{
93057     text: 'Save',
93058     formBind: true,
93059     handler: function(){
93060         if(fp.getForm().isValid()){
93061             fp.getForm().submit({
93062                 url: 'form-submit.php',
93063                 waitMsg: 'Submitting your data...',
93064                 success: function(form, action){
93065                     // server responded with success = true
93066                     var result = action.{@link #result};
93067                 },
93068                 failure: function(form, action){
93069                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
93070                         Ext.Msg.alert('Error',
93071                             'Status:'+action.{@link #response}.status+': '+
93072                             action.{@link #response}.statusText);
93073                     }
93074                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
93075                         // server responded with success = false
93076                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
93077                     }
93078                 }
93079             });
93080         }
93081     }
93082 },{
93083     text: 'Reset',
93084     handler: function(){
93085         fp.getForm().reset();
93086     }
93087 }]
93088      * </code></pre>
93089      * @property failureType
93090      * @type {String}
93091      */
93092
93093     /**
93094      * The raw XMLHttpRequest object used to perform the action.
93095      * @property response
93096      * @type {Object}
93097      */
93098
93099     /**
93100      * The decoded response object containing a boolean <tt>success</tt> property and
93101      * other, action-specific properties.
93102      * @property result
93103      * @type {Object}
93104      */
93105
93106     /**
93107      * Creates new Action.
93108      * @param {Object} config (optional) Config object.
93109      */
93110     constructor: function(config) {
93111         if (config) {
93112             Ext.apply(this, config);
93113         }
93114
93115         // Normalize the params option to an Object
93116         var params = config.params;
93117         if (Ext.isString(params)) {
93118             this.params = Ext.Object.fromQueryString(params);
93119         }
93120     },
93121
93122     /**
93123      * Invokes this action using the current configuration.
93124      */
93125     run: Ext.emptyFn,
93126
93127     /**
93128      * @private
93129      * @method onSuccess
93130      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
93131      * @param {Object} response
93132      */
93133
93134     /**
93135      * @private
93136      * @method handleResponse
93137      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
93138      * @param {Object} response
93139      */
93140
93141     /**
93142      * @private
93143      * Handles a failure response.
93144      * @param {Object} response
93145      */
93146     onFailure : function(response){
93147         this.response = response;
93148         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
93149         this.form.afterAction(this, false);
93150     },
93151
93152     /**
93153      * @private
93154      * Validates that a response contains either responseText or responseXML and invokes
93155      * {@link #handleResponse} to build the result object.
93156      * @param {Object} response The raw response object.
93157      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
93158      *                         the response had empty responseText and responseXML.
93159      */
93160     processResponse : function(response){
93161         this.response = response;
93162         if (!response.responseText && !response.responseXML) {
93163             return true;
93164         }
93165         return (this.result = this.handleResponse(response));
93166     },
93167
93168     /**
93169      * @private
93170      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
93171      * @return {String} The URL.
93172      */
93173     getUrl: function() {
93174         return this.url || this.form.url;
93175     },
93176
93177     /**
93178      * @private
93179      * Determine the HTTP method to be used for the request.
93180      * @return {String} The HTTP method
93181      */
93182     getMethod: function() {
93183         return (this.method || this.form.method || 'POST').toUpperCase();
93184     },
93185
93186     /**
93187      * @private
93188      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
93189      * Items in params override items of the same name in baseParams.
93190      * @return {Object} the full set of parameters
93191      */
93192     getParams: function() {
93193         return Ext.apply({}, this.params, this.form.baseParams);
93194     },
93195
93196     /**
93197      * @private
93198      * Creates a callback object.
93199      */
93200     createCallback: function() {
93201         var me = this,
93202             undef,
93203             form = me.form;
93204         return {
93205             success: me.onSuccess,
93206             failure: me.onFailure,
93207             scope: me,
93208             timeout: (this.timeout * 1000) || (form.timeout * 1000),
93209             upload: form.fileUpload ? me.onSuccess : undef
93210         };
93211     },
93212
93213     statics: {
93214         /**
93215          * @property CLIENT_INVALID
93216          * Failure type returned when client side validation of the Form fails
93217          * thus aborting a submit action. Client side validation is performed unless
93218          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
93219          * @type {String}
93220          * @static
93221          */
93222         CLIENT_INVALID: 'client',
93223
93224         /**
93225          * @property SERVER_INVALID
93226          * <p>Failure type returned when server side processing fails and the {@link #result}'s
93227          * <tt>success</tt> property is set to <tt>false</tt>.</p>
93228          * <p>In the case of a form submission, field-specific error messages may be returned in the
93229          * {@link #result}'s <tt>errors</tt> property.</p>
93230          * @type {String}
93231          * @static
93232          */
93233         SERVER_INVALID: 'server',
93234
93235         /**
93236          * @property CONNECT_FAILURE
93237          * Failure type returned when a communication error happens when attempting
93238          * to send a request to the remote server. The {@link #response} may be examined to
93239          * provide further information.
93240          * @type {String}
93241          * @static
93242          */
93243         CONNECT_FAILURE: 'connect',
93244
93245         /**
93246          * @property LOAD_FAILURE
93247          * Failure type returned when the response's <tt>success</tt>
93248          * property is set to <tt>false</tt>, or no field values are returned in the response's
93249          * <tt>data</tt> property.
93250          * @type {String}
93251          * @static
93252          */
93253         LOAD_FAILURE: 'load'
93254
93255
93256     }
93257 });
93258
93259 /**
93260  * @class Ext.form.action.Submit
93261  * @extends Ext.form.action.Action
93262  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
93263  * and processes the returned response.</p>
93264  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
93265  * {@link Ext.form.Basic#submit submit}ting.</p>
93266  * <p><u><b>Response Packet Criteria</b></u></p>
93267  * <p>A response packet may contain:
93268  * <div class="mdetail-params"><ul>
93269  * <li><b><code>success</code></b> property : Boolean
93270  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
93271  * <li><b><code>errors</code></b> property : Object
93272  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
93273  * which is optional, contains error messages for invalid fields.</div></li>
93274  * </ul></div>
93275  * <p><u><b>JSON Packets</b></u></p>
93276  * <p>By default, response packets are assumed to be JSON, so a typical response
93277  * packet may look like this:</p><pre><code>
93278 {
93279     success: false,
93280     errors: {
93281         clientCode: "Client not found",
93282         portOfLoading: "This field must not be null"
93283     }
93284 }</code></pre>
93285  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
93286  * or event handler methods. The object decoded from this JSON is available in the
93287  * {@link Ext.form.action.Action#result result} property.</p>
93288  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
93289     errorReader: new Ext.data.reader.Xml({
93290             record : 'field',
93291             success: '@success'
93292         }, [
93293             'id', 'msg'
93294         ]
93295     )
93296 </code></pre>
93297  * <p>then the results may be sent back in XML format:</p><pre><code>
93298 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
93299 &lt;message success="false"&gt;
93300 &lt;errors&gt;
93301     &lt;field&gt;
93302         &lt;id&gt;clientCode&lt;/id&gt;
93303         &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;
93304     &lt;/field&gt;
93305     &lt;field&gt;
93306         &lt;id&gt;portOfLoading&lt;/id&gt;
93307         &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;
93308     &lt;/field&gt;
93309 &lt;/errors&gt;
93310 &lt;/message&gt;
93311 </code></pre>
93312  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
93313  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
93314  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
93315  */
93316 Ext.define('Ext.form.action.Submit', {
93317     extend:'Ext.form.action.Action',
93318     alternateClassName: 'Ext.form.Action.Submit',
93319     alias: 'formaction.submit',
93320
93321     type: 'submit',
93322
93323     /**
93324      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
93325      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
93326      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
93327      */
93328
93329     // inherit docs
93330     run : function(){
93331         var form = this.form;
93332         if (this.clientValidation === false || form.isValid()) {
93333             this.doSubmit();
93334         } else {
93335             // client validation failed
93336             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
93337             form.afterAction(this, false);
93338         }
93339     },
93340
93341     /**
93342      * @private
93343      * Perform the submit of the form data.
93344      */
93345     doSubmit: function() {
93346         var formEl,
93347             ajaxOptions = Ext.apply(this.createCallback(), {
93348                 url: this.getUrl(),
93349                 method: this.getMethod(),
93350                 headers: this.headers
93351             });
93352
93353         // For uploads we need to create an actual form that contains the file upload fields,
93354         // and pass that to the ajax call so it can do its iframe-based submit method.
93355         if (this.form.hasUpload()) {
93356             formEl = ajaxOptions.form = this.buildForm();
93357             ajaxOptions.isUpload = true;
93358         } else {
93359             ajaxOptions.params = this.getParams();
93360         }
93361
93362         Ext.Ajax.request(ajaxOptions);
93363
93364         if (formEl) {
93365             Ext.removeNode(formEl);
93366         }
93367     },
93368
93369     /**
93370      * @private
93371      * Build the full set of parameters from the field values plus any additional configured params.
93372      */
93373     getParams: function() {
93374         var nope = false,
93375             configParams = this.callParent(),
93376             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
93377         return Ext.apply({}, fieldParams, configParams);
93378     },
93379
93380     /**
93381      * @private
93382      * Build a form element containing fields corresponding to all the parameters to be
93383      * submitted (everything returned by {@link #getParams}.
93384      * NOTE: the form element is automatically added to the DOM, so any code that uses
93385      * it must remove it from the DOM after finishing with it.
93386      * @return HTMLFormElement
93387      */
93388     buildForm: function() {
93389         var fieldsSpec = [],
93390             formSpec,
93391             formEl,
93392             basicForm = this.form,
93393             params = this.getParams(),
93394             uploadFields = [];
93395
93396         basicForm.getFields().each(function(field) {
93397             if (field.isFileUpload()) {
93398                 uploadFields.push(field);
93399             }
93400         });
93401
93402         function addField(name, val) {
93403             fieldsSpec.push({
93404                 tag: 'input',
93405                 type: 'hidden',
93406                 name: name,
93407                 value: Ext.String.htmlEncode(val)
93408             });
93409         }
93410
93411         // Add the form field values
93412         Ext.iterate(params, function(key, val) {
93413             if (Ext.isArray(val)) {
93414                 Ext.each(val, function(v) {
93415                     addField(key, v);
93416                 });
93417             } else {
93418                 addField(key, val);
93419             }
93420         });
93421
93422         formSpec = {
93423             tag: 'form',
93424             action: this.getUrl(),
93425             method: this.getMethod(),
93426             target: this.target || '_self',
93427             style: 'display:none',
93428             cn: fieldsSpec
93429         };
93430
93431         // Set the proper encoding for file uploads
93432         if (uploadFields.length) {
93433             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
93434         }
93435
93436         // Create the form
93437         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
93438
93439         // Special handling for file upload fields: since browser security measures prevent setting
93440         // their values programatically, and prevent carrying their selected values over when cloning,
93441         // we have to move the actual field instances out of their components and into the form.
93442         Ext.Array.each(uploadFields, function(field) {
93443             if (field.rendered) { // can only have a selected file value after being rendered
93444                 formEl.appendChild(field.extractFileInput());
93445             }
93446         });
93447
93448         return formEl;
93449     },
93450
93451
93452
93453     /**
93454      * @private
93455      */
93456     onSuccess: function(response) {
93457         var form = this.form,
93458             success = true,
93459             result = this.processResponse(response);
93460         if (result !== true && !result.success) {
93461             if (result.errors) {
93462                 form.markInvalid(result.errors);
93463             }
93464             this.failureType = Ext.form.action.Action.SERVER_INVALID;
93465             success = false;
93466         }
93467         form.afterAction(this, success);
93468     },
93469
93470     /**
93471      * @private
93472      */
93473     handleResponse: function(response) {
93474         var form = this.form,
93475             errorReader = form.errorReader,
93476             rs, errors, i, len, records;
93477         if (errorReader) {
93478             rs = errorReader.read(response);
93479             records = rs.records;
93480             errors = [];
93481             if (records) {
93482                 for(i = 0, len = records.length; i < len; i++) {
93483                     errors[i] = records[i].data;
93484                 }
93485             }
93486             if (errors.length < 1) {
93487                 errors = null;
93488             }
93489             return {
93490                 success : rs.success,
93491                 errors : errors
93492             };
93493         }
93494         return Ext.decode(response.responseText);
93495     }
93496 });
93497
93498 /**
93499  * @class Ext.util.ComponentDragger
93500  * @extends Ext.dd.DragTracker
93501  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
93502  * <p>This is configured with a Component to be made draggable, and a config object for the
93503  * {@link Ext.dd.DragTracker} class.</p>
93504  * <p>A {@link #delegate} may be provided which may be either the element to use as the mousedown target
93505  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
93506  */
93507 Ext.define('Ext.util.ComponentDragger', {
93508
93509     /**
93510      * @cfg {Boolean} constrain
93511      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
93512      */
93513
93514     /**
93515      * @cfg {String/Ext.Element} delegate
93516      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
93517      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
93518      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
93519      */
93520
93521     /**
93522      * @cfg {Boolean} constrainDelegate
93523      * Specify as <code>true</code> to constrain the drag handles within the {@link #constrainTo} region.
93524      */
93525
93526     extend: 'Ext.dd.DragTracker',
93527
93528     autoStart: 500,
93529
93530     /**
93531      * Creates new ComponentDragger.
93532      * @param {Object} comp The Component to provide dragging for.
93533      * @param {Object} config (optional) Config object
93534      */
93535     constructor: function(comp, config) {
93536         this.comp = comp;
93537         this.initialConstrainTo = config.constrainTo;
93538         this.callParent([ config ]);
93539     },
93540
93541     onStart: function(e) {
93542         var me = this,
93543             comp = me.comp;
93544
93545         // Cache the start [X, Y] array
93546         this.startPosition = comp.getPosition();
93547
93548         // If client Component has a ghost method to show a lightweight version of itself
93549         // then use that as a drag proxy unless configured to liveDrag.
93550         if (comp.ghost && !comp.liveDrag) {
93551              me.proxy = comp.ghost();
93552              me.dragTarget = me.proxy.header.el;
93553         }
93554
93555         // Set the constrainTo Region before we start dragging.
93556         if (me.constrain || me.constrainDelegate) {
93557             me.constrainTo = me.calculateConstrainRegion();
93558         }
93559     },
93560
93561     calculateConstrainRegion: function() {
93562         var me = this,
93563             comp = me.comp,
93564             c = me.initialConstrainTo,
93565             delegateRegion,
93566             elRegion,
93567             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
93568
93569         // The configured constrainTo might be a Region or an element
93570         if (!(c instanceof Ext.util.Region)) {
93571             c =  Ext.fly(c).getViewRegion();
93572         }
93573
93574         // Reduce the constrain region to allow for shadow
93575         if (shadowSize) {
93576             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
93577         }
93578
93579         // If they only want to constrain the *delegate* to within the constrain region,
93580         // adjust the region to be larger based on the insets of the delegate from the outer
93581         // edges of the Component.
93582         if (!me.constrainDelegate) {
93583             delegateRegion = Ext.fly(me.dragTarget).getRegion();
93584             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
93585
93586             c.adjust(
93587                 delegateRegion.top - elRegion.top,
93588                 delegateRegion.right - elRegion.right,
93589                 delegateRegion.bottom - elRegion.bottom,
93590                 delegateRegion.left - elRegion.left
93591             );
93592         }
93593         return c;
93594     },
93595
93596     // Move either the ghost Component or the target Component to its new position on drag
93597     onDrag: function(e) {
93598         var me = this,
93599             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
93600             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
93601
93602         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
93603     },
93604
93605     onEnd: function(e) {
93606         if (this.proxy && !this.comp.liveDrag) {
93607             this.comp.unghost();
93608         }
93609     }
93610 });
93611 /**
93612  * A mixin which allows a component to be configured and decorated with a label and/or error message as is
93613  * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
93614  * to let them be managed by the Field layout.
93615  *
93616  * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
93617  * is more likely you will want to use one of the component classes that import this mixin, such as
93618  * Ext.form.field.Base or Ext.form.FieldContainer.
93619  *
93620  * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
93621  * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
93622  * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
93623  * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
93624  * two.)
93625  *
93626  * Component classes which use this mixin should use the Field layout
93627  * or a derivation thereof to properly size and position the label and message according to the component config.
93628  * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
93629  * set up correctly.
93630  *
93631  * @docauthor Jason Johnston <jason@sencha.com>
93632  */
93633 Ext.define("Ext.form.Labelable", {
93634     requires: ['Ext.XTemplate'],
93635
93636     /**
93637      * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
93638      * The rendering template for the field decorations. Component classes using this mixin should include
93639      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
93640      * {@link #getSubTplMarkup} method to generate the field body content.
93641      */
93642     labelableRenderTpl: [
93643         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
93644             '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
93645                 '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
93646                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
93647             '</label>',
93648         '</tpl>',
93649         '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
93650         '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
93651         '<div class="{clearCls}" role="presentation"><!-- --></div>',
93652         {
93653             compiled: true,
93654             disableFormats: true
93655         }
93656     ],
93657
93658     /**
93659      * @cfg {Ext.XTemplate} activeErrorsTpl
93660      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
93661      * into a single HTML string. By default this renders each message as an item in an unordered list.
93662      */
93663     activeErrorsTpl: [
93664         '<tpl if="errors && errors.length">',
93665             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
93666         '</tpl>'
93667     ],
93668
93669     /**
93670      * @property isFieldLabelable
93671      * @type Boolean
93672      * Flag denoting that this object is labelable as a field. Always true.
93673      */
93674     isFieldLabelable: true,
93675
93676     /**
93677      * @cfg {String} [formItemCls='x-form-item']
93678      * A CSS class to be applied to the outermost element to denote that it is participating in the form
93679      * field layout.
93680      */
93681     formItemCls: Ext.baseCSSPrefix + 'form-item',
93682
93683     /**
93684      * @cfg {String} [labelCls='x-form-item-label']
93685      * The CSS class to be applied to the label element.
93686      * This (single) CSS class is used to formulate the renderSelector and drives the field
93687      * layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
93688      * additional classes, use {@link #labelClsExtra}.
93689      */
93690     labelCls: Ext.baseCSSPrefix + 'form-item-label',
93691
93692     /**
93693      * @cfg {String} labelClsExtra
93694      * An optional string of one or more additional CSS classes to add to the label element.
93695      * Defaults to empty.
93696      */
93697
93698     /**
93699      * @cfg {String} [errorMsgCls='x-form-error-msg']
93700      * The CSS class to be applied to the error message element.
93701      */
93702     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
93703
93704     /**
93705      * @cfg {String} [baseBodyCls='x-form-item-body']
93706      * The CSS class to be applied to the body content element.
93707      */
93708     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
93709
93710     /**
93711      * @cfg {String} fieldBodyCls
93712      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
93713      */
93714     fieldBodyCls: '',
93715
93716     /**
93717      * @cfg {String} [clearCls='x-clear']
93718      * The CSS class to be applied to the special clearing div rendered directly after the field
93719      * contents wrapper to provide field clearing.
93720      */
93721     clearCls: Ext.baseCSSPrefix + 'clear',
93722
93723     /**
93724      * @cfg {String} [invalidCls='x-form-invalid']
93725      * The CSS class to use when marking the component invalid.
93726      */
93727     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
93728
93729     /**
93730      * @cfg {String} fieldLabel
93731      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
93732      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
93733      * configs.
93734      */
93735     fieldLabel: undefined,
93736
93737     /**
93738      * @cfg {String} labelAlign
93739      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
93740      * <ul>
93741      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
93742      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
93743      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
93744      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
93745      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
93746      * </ul>
93747      */
93748     labelAlign : 'left',
93749
93750     /**
93751      * @cfg {Number} labelWidth
93752      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
93753      * to "left" or "right".
93754      */
93755     labelWidth: 100,
93756
93757     /**
93758      * @cfg {Number} labelPad
93759      * The amount of space in pixels between the {@link #fieldLabel} and the input field.
93760      */
93761     labelPad : 5,
93762
93763     /**
93764      * @cfg {String} labelSeparator
93765      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
93766      */
93767     labelSeparator : ':',
93768
93769     /**
93770      * @cfg {String} labelStyle
93771      * A CSS style specification string to apply directly to this field's label.
93772      */
93773
93774     /**
93775      * @cfg {Boolean} hideLabel
93776      * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
93777      * Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
93778      */
93779     hideLabel: false,
93780
93781     /**
93782      * @cfg {Boolean} hideEmptyLabel
93783      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
93784      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
93785      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
93786      * to line up with other labeled fields in the same form.</p>
93787      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
93788      * the {@link #hideLabel} config to <tt>true</tt>.</p>
93789      */
93790     hideEmptyLabel: true,
93791
93792     /**
93793      * @cfg {Boolean} preventMark
93794      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
93795      */
93796     preventMark: false,
93797
93798     /**
93799      * @cfg {Boolean} autoFitErrors
93800      * Whether to adjust the component's body area to make room for 'side' or 'under'
93801      * {@link #msgTarget error messages}.
93802      */
93803     autoFitErrors: true,
93804
93805     /**
93806      * @cfg {String} msgTarget <p>The location where the error message text should display.
93807      * Must be one of the following values:</p>
93808      * <div class="mdetail-params"><ul>
93809      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
93810      * <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>
93811      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
93812      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
93813      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
93814      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
93815      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
93816      * </ul></div>
93817      */
93818     msgTarget: 'qtip',
93819
93820     /**
93821      * @cfg {String} activeError
93822      * If specified, then the component will be displayed with this value as its active error when
93823      * first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to
93824      * change it after component creation.
93825      */
93826
93827
93828     /**
93829      * Performs initialization of this mixin. Component classes using this mixin should call this method
93830      * during their own initialization.
93831      */
93832     initLabelable: function() {
93833         this.addCls(this.formItemCls);
93834
93835         this.addEvents(
93836             /**
93837              * @event errorchange
93838              * Fires when the active error message is changed via {@link #setActiveError}.
93839              * @param {Ext.form.Labelable} this
93840              * @param {String} error The active error message
93841              */
93842             'errorchange'
93843         );
93844     },
93845
93846     /**
93847      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
93848      * overridden to provide
93849      * @return {String} The configured field label, or empty string if not defined
93850      */
93851     getFieldLabel: function() {
93852         return this.fieldLabel || '';
93853     },
93854
93855     /**
93856      * @protected
93857      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
93858      * @return {Object} The template arguments
93859      */
93860     getLabelableRenderData: function() {
93861         var me = this,
93862             labelAlign = me.labelAlign,
93863             labelCls = me.labelCls,
93864             labelClsExtra = me.labelClsExtra,
93865             labelPad = me.labelPad,
93866             labelStyle;
93867
93868         // Calculate label styles up front rather than in the Field layout for speed; this
93869         // is safe because label alignment/width/pad are not expected to change.
93870         if (labelAlign === 'top') {
93871             labelStyle = 'margin-bottom:' + labelPad + 'px;';
93872         } else {
93873             labelStyle = 'margin-right:' + labelPad + 'px;';
93874             // Add the width for border-box browsers; will be set by the Field layout for content-box
93875             if (Ext.isBorderBox) {
93876                 labelStyle += 'width:' + me.labelWidth + 'px;';
93877             }
93878         }
93879
93880         return Ext.copyTo(
93881             {
93882                 inputId: me.getInputId(),
93883                 fieldLabel: me.getFieldLabel(),
93884                 labelCls: labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls,
93885                 labelStyle: labelStyle + (me.labelStyle || ''),
93886                 subTplMarkup: me.getSubTplMarkup()
93887             },
93888             me,
93889             'hideLabel,hideEmptyLabel,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
93890             true
93891         );
93892     },
93893
93894     onLabelableRender: function () {
93895         this.addChildEls(
93896             /**
93897              * @property labelEl
93898              * @type Ext.Element
93899              * The label Element for this component. Only available after the component has been rendered.
93900              */
93901             'labelEl',
93902
93903             /**
93904              * @property bodyEl
93905              * @type Ext.Element
93906              * The div Element wrapping the component's contents. Only available after the component has been rendered.
93907              */
93908             'bodyEl',
93909
93910             /**
93911              * @property errorEl
93912              * @type Ext.Element
93913              * The div Element that will contain the component's error message(s). Note that depending on the
93914              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
93915              * presentation, but will always be present in the DOM for use by assistive technologies.
93916              */
93917             'errorEl'
93918         );
93919     },
93920
93921     /**
93922      * @protected
93923      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
93924      * be implemented by classes including this mixin as needed.
93925      * @return {String} The markup to be inserted
93926      */
93927     getSubTplMarkup: function() {
93928         return '';
93929     },
93930
93931     /**
93932      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
93933      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
93934      * @return {String} The input id
93935      */
93936     getInputId: function() {
93937         return '';
93938     },
93939
93940     /**
93941      * Gets the active error message for this component, if any. This does not trigger
93942      * validation on its own, it merely returns any message that the component may already hold.
93943      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
93944      */
93945     getActiveError : function() {
93946         return this.activeError || '';
93947     },
93948
93949     /**
93950      * Tells whether the field currently has an active error message. This does not trigger
93951      * validation on its own, it merely looks for any message that the component may already hold.
93952      * @return {Boolean}
93953      */
93954     hasActiveError: function() {
93955         return !!this.getActiveError();
93956     },
93957
93958     /**
93959      * Sets the active error message to the given string. This replaces the entire error message
93960      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
93961      * messages and formats them according to the {@link #activeErrorsTpl}.
93962      *
93963      * Note that this only updates the error message element's text and attributes, you'll have
93964      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93965      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93966      *
93967      * @param {String} msg The error message
93968      */
93969     setActiveError: function(msg) {
93970         this.activeError = msg;
93971         this.activeErrors = [msg];
93972         this.renderActiveError();
93973     },
93974
93975     /**
93976      * Gets an Array of any active error messages currently applied to the field. This does not trigger
93977      * validation on its own, it merely returns any messages that the component may already hold.
93978      * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is returned.
93979      */
93980     getActiveErrors: function() {
93981         return this.activeErrors || [];
93982     },
93983
93984     /**
93985      * Set the active error message to an Array of error messages. The messages are formatted into
93986      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
93987      * which allows setting the entire error contents with a single string.
93988      *
93989      * Note that this only updates the error message element's text and attributes, you'll have
93990      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93991      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93992      *
93993      * @param {String[]} errors The error messages
93994      */
93995     setActiveErrors: function(errors) {
93996         this.activeErrors = errors;
93997         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
93998         this.renderActiveError();
93999     },
94000
94001     /**
94002      * Clears the active error message(s).
94003      *
94004      * Note that this only clears the error message element's text and attributes, you'll have
94005      * to call doComponentLayout to actually update the field's layout to match. If the field extends
94006      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
94007      */
94008     unsetActiveError: function() {
94009         delete this.activeError;
94010         delete this.activeErrors;
94011         this.renderActiveError();
94012     },
94013
94014     /**
94015      * @private
94016      * Updates the rendered DOM to match the current activeError. This only updates the content and
94017      * attributes, you'll have to call doComponentLayout to actually update the display.
94018      */
94019     renderActiveError: function() {
94020         var me = this,
94021             activeError = me.getActiveError(),
94022             hasError = !!activeError;
94023
94024         if (activeError !== me.lastActiveError) {
94025             me.fireEvent('errorchange', me, activeError);
94026             me.lastActiveError = activeError;
94027         }
94028
94029         if (me.rendered && !me.isDestroyed && !me.preventMark) {
94030             // Add/remove invalid class
94031             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
94032
94033             // Update the aria-invalid attribute
94034             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
94035
94036             // Update the errorEl with the error message text
94037             me.errorEl.dom.innerHTML = activeError;
94038         }
94039     },
94040
94041     /**
94042      * Applies a set of default configuration values to this Labelable instance. For each of the
94043      * properties in the given object, check if this component hasOwnProperty that config; if not
94044      * then it's inheriting a default value from its prototype and we should apply the default value.
94045      * @param {Object} defaults The defaults to apply to the object.
94046      */
94047     setFieldDefaults: function(defaults) {
94048         var me = this;
94049         Ext.iterate(defaults, function(key, val) {
94050             if (!me.hasOwnProperty(key)) {
94051                 me[key] = val;
94052             }
94053         });
94054     },
94055
94056     /**
94057      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
94058      * Note for implementors: if at all possible this method should be overridden with a custom implementation
94059      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
94060      */
94061     getBodyNaturalWidth: function() {
94062         return this.bodyEl.getWidth();
94063     }
94064
94065 });
94066
94067 /**
94068  * @docauthor Jason Johnston <jason@sencha.com>
94069  *
94070  * This mixin provides a common interface for the logical behavior and state of form fields, including:
94071  *
94072  * - Getter and setter methods for field values
94073  * - Events and methods for tracking value and validity changes
94074  * - Methods for triggering validation
94075  *
94076  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
94077  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
94078  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
94079  * plus methods that bind the Field value getters and setters to the input field's value.
94080  *
94081  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
94082  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
94083  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
94084  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
94085  * is called during the component's initialization.
94086  */
94087 Ext.define('Ext.form.field.Field', {
94088     /**
94089      * @property {Boolean} isFormField
94090      * Flag denoting that this component is a Field. Always true.
94091      */
94092     isFormField : true,
94093
94094     /**
94095      * @cfg {Object} value
94096      * A value to initialize this field with.
94097      */
94098
94099     /**
94100      * @cfg {String} name
94101      * The name of the field. By default this is used as the parameter name when including the
94102      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
94103      * being included in the form submit, set {@link #submitValue} to false.
94104      */
94105
94106     /**
94107      * @cfg {Boolean} disabled
94108      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
94109      */
94110     disabled : false,
94111
94112     /**
94113      * @cfg {Boolean} submitValue
94114      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
94115      * not disabled.
94116      */
94117     submitValue: true,
94118
94119     /**
94120      * @cfg {Boolean} validateOnChange
94121      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
94122      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
94123      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
94124      * typing.
94125      *
94126      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
94127      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
94128      * manually.
94129      *
94130      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
94131      * detected.
94132      */
94133     validateOnChange: true,
94134
94135     /**
94136      * @private
94137      */
94138     suspendCheckChange: 0,
94139
94140     /**
94141      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
94142      * their own initialization process.
94143      */
94144     initField: function() {
94145         this.addEvents(
94146             /**
94147              * @event change
94148              * Fires when a user-initiated change is detected in the value of the field.
94149              * @param {Ext.form.field.Field} this
94150              * @param {Object} newValue The new value
94151              * @param {Object} oldValue The original value
94152              */
94153             'change',
94154             /**
94155              * @event validitychange
94156              * Fires when a change in the field's validity is detected.
94157              * @param {Ext.form.field.Field} this
94158              * @param {Boolean} isValid Whether or not the field is now valid
94159              */
94160             'validitychange',
94161             /**
94162              * @event dirtychange
94163              * Fires when a change in the field's {@link #isDirty} state is detected.
94164              * @param {Ext.form.field.Field} this
94165              * @param {Boolean} isDirty Whether or not the field is now dirty
94166              */
94167             'dirtychange'
94168         );
94169
94170         this.initValue();
94171     },
94172
94173     /**
94174      * Initializes the field's value based on the initial config.
94175      */
94176     initValue: function() {
94177         var me = this;
94178
94179         /**
94180          * @property {Object} originalValue
94181          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
94182          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
94183          */
94184         me.originalValue = me.lastValue = me.value;
94185
94186         // Set the initial value - prevent validation on initial set
94187         me.suspendCheckChange++;
94188         me.setValue(me.value);
94189         me.suspendCheckChange--;
94190     },
94191
94192     /**
94193      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
94194      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
94195      * @return {String} name The field {@link Ext.form.field.Field#name name}
94196      */
94197     getName: function() {
94198         return this.name;
94199     },
94200
94201     /**
94202      * Returns the current data value of the field. The type of value returned is particular to the type of the
94203      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
94204      * @return {Object} value The field value
94205      */
94206     getValue: function() {
94207         return this.value;
94208     },
94209
94210     /**
94211      * Sets a data value into the field and runs the change detection and validation.
94212      * @param {Object} value The value to set
94213      * @return {Ext.form.field.Field} this
94214      */
94215     setValue: function(value) {
94216         var me = this;
94217         me.value = value;
94218         me.checkChange();
94219         return me;
94220     },
94221
94222     /**
94223      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
94224      * to provide custom comparison logic appropriate for the particular field's data type.
94225      * @param {Object} value1 The first value to compare
94226      * @param {Object} value2 The second value to compare
94227      * @return {Boolean} True if the values are equal, false if inequal.
94228      */
94229     isEqual: function(value1, value2) {
94230         return String(value1) === String(value2);
94231     },
94232     
94233     /**
94234      * Returns whether two values are logically equal.
94235      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
94236      * @private
94237      * @param {Object} value1 The first value to compare
94238      * @param {Object} value2 The second value to compare
94239      * @return {Boolean} True if the values are equal, false if inequal.
94240      */
94241     isEqualAsString: function(value1, value2){
94242         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
94243     },
94244
94245     /**
94246      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
94247      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
94248      * its current stringified value. More advanced field implementations may return more than one name-value pair.
94249      *
94250      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
94251      * validated}.
94252      *
94253      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
94254      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
94255      * submitted.
94256      */
94257     getSubmitData: function() {
94258         var me = this,
94259             data = null;
94260         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
94261             data = {};
94262             data[me.getName()] = '' + me.getValue();
94263         }
94264         return data;
94265     },
94266
94267     /**
94268      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
94269      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
94270      * being this field's {@link #getName name} and the value being its current data value. More advanced field
94271      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
94272      * field names in the Model.
94273      *
94274      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
94275      * validated}.
94276      *
94277      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
94278      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
94279      * submitted.
94280      */
94281     getModelData: function() {
94282         var me = this,
94283             data = null;
94284         if (!me.disabled && !me.isFileUpload()) {
94285             data = {};
94286             data[me.getName()] = me.getValue();
94287         }
94288         return data;
94289     },
94290
94291     /**
94292      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
94293      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
94294      */
94295     reset : function(){
94296         var me = this;
94297
94298         me.setValue(me.originalValue);
94299         me.clearInvalid();
94300         // delete here so we reset back to the original state
94301         delete me.wasValid;
94302     },
94303
94304     /**
94305      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
94306      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
94307      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
94308      */
94309     resetOriginalValue: function() {
94310         this.originalValue = this.getValue();
94311         this.checkDirty();
94312     },
94313
94314     /**
94315      * Checks whether the value of the field has changed since the last time it was checked.
94316      * If the value has changed, it:
94317      *
94318      * 1. Fires the {@link #change change event},
94319      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
94320      *    {@link #validitychange validitychange event} if the validity has changed, and
94321      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
94322      *    if it has changed.
94323      */
94324     checkChange: function() {
94325         if (!this.suspendCheckChange) {
94326             var me = this,
94327                 newVal = me.getValue(),
94328                 oldVal = me.lastValue;
94329             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
94330                 me.lastValue = newVal;
94331                 me.fireEvent('change', me, newVal, oldVal);
94332                 me.onChange(newVal, oldVal);
94333             }
94334         }
94335     },
94336
94337     /**
94338      * @private
94339      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
94340      * config is enabled, and invokes the dirty check.
94341      */
94342     onChange: function(newVal, oldVal) {
94343         if (this.validateOnChange) {
94344             this.validate();
94345         }
94346         this.checkDirty();
94347     },
94348
94349     /**
94350      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
94351      * Will always return false if the field is disabled.
94352      *
94353      * Note that if the owning {@link Ext.form.Basic form} was configured with
94354      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
94355      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
94356      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
94357      * false otherwise.
94358      */
94359     isDirty : function() {
94360         var me = this;
94361         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
94362     },
94363
94364     /**
94365      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
94366      * fires the {@link #dirtychange} event.
94367      */
94368     checkDirty: function() {
94369         var me = this,
94370             isDirty = me.isDirty();
94371         if (isDirty !== me.wasDirty) {
94372             me.fireEvent('dirtychange', me, isDirty);
94373             me.onDirtyChange(isDirty);
94374             me.wasDirty = isDirty;
94375         }
94376     },
94377
94378     /**
94379      * @private Called when the field's dirty state changes.
94380      * @param {Boolean} isDirty
94381      */
94382     onDirtyChange: Ext.emptyFn,
94383
94384     /**
94385      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
94386      * internally during validation and would not usually need to be used manually.
94387      *
94388      * Each subclass should override or augment the return value to provide their own errors.
94389      *
94390      * @param {Object} value The value to get errors for (defaults to the current field value)
94391      * @return {String[]} All error messages for this field; an empty Array if none.
94392      */
94393     getErrors: function(value) {
94394         return [];
94395     },
94396
94397     /**
94398      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
94399      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
94400      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
94401      *
94402      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
94403      * message display.
94404      *
94405      * @return {Boolean} True if the value is valid, else false
94406      */
94407     isValid : function() {
94408         var me = this;
94409         return me.disabled || Ext.isEmpty(me.getErrors());
94410     },
94411
94412     /**
94413      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
94414      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
94415      * **Note**: {@link #disabled} fields are always treated as valid.
94416      *
94417      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
94418      * To validate without side-effects, use {@link #isValid}.
94419      *
94420      * @return {Boolean} True if the value is valid, else false
94421      */
94422     validate : function() {
94423         var me = this,
94424             isValid = me.isValid();
94425         if (isValid !== me.wasValid) {
94426             me.wasValid = isValid;
94427             me.fireEvent('validitychange', me, isValid);
94428         }
94429         return isValid;
94430     },
94431
94432     /**
94433      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
94434      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
94435      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
94436      * change.
94437      * @param {Object} fn A function containing the transaction code
94438      */
94439     batchChanges: function(fn) {
94440         try {
94441             this.suspendCheckChange++;
94442             fn();
94443         } catch(e){
94444             throw e;
94445         } finally {
94446             this.suspendCheckChange--;
94447         }
94448         this.checkChange();
94449     },
94450
94451     /**
94452      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
94453      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
94454      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
94455      * input element.
94456      * @return {Boolean}
94457      */
94458     isFileUpload: function() {
94459         return false;
94460     },
94461
94462     /**
94463      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
94464      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
94465      * returned, so this method should also create a replacement.
94466      * @return {HTMLElement}
94467      */
94468     extractFileInput: function() {
94469         return null;
94470     },
94471
94472     /**
94473      * @method markInvalid
94474      * Associate one or more error messages with this field. Components using this mixin should implement this method to
94475      * update the component's rendering to display the messages.
94476      *
94477      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
94478      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
94479      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
94480      *
94481      * @param {String/String[]} errors The error message(s) for the field.
94482      */
94483     markInvalid: Ext.emptyFn,
94484
94485     /**
94486      * @method clearInvalid
94487      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
94488      * update the components rendering to clear any existing messages.
94489      *
94490      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
94491      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
94492      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
94493      */
94494     clearInvalid: Ext.emptyFn
94495
94496 });
94497
94498 /**
94499  * @class Ext.layout.component.field.Field
94500  * @extends Ext.layout.component.Component
94501  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
94502  * the form control, label, and error message treatment.
94503  * @private
94504  */
94505 Ext.define('Ext.layout.component.field.Field', {
94506
94507     /* Begin Definitions */
94508
94509     alias: ['layout.field'],
94510
94511     extend: 'Ext.layout.component.Component',
94512
94513     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
94514
94515     /* End Definitions */
94516
94517     type: 'field',
94518
94519     beforeLayout: function(width, height) {
94520         var me = this;
94521         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
94522     },
94523
94524     onLayout: function(width, height) {
94525         var me = this,
94526             owner = me.owner,
94527             labelStrategy = me.getLabelStrategy(),
94528             errorStrategy = me.getErrorStrategy(),
94529             isDefined = Ext.isDefined,
94530             isNumber = Ext.isNumber,
94531             lastSize, autoWidth, autoHeight, info, undef;
94532
94533         lastSize = me.lastComponentSize || {};
94534         if (!isDefined(width)) {
94535             width = lastSize.width;
94536             if (width < 0) { //first pass lastComponentSize.width is -Infinity
94537                 width = undef;
94538             }
94539         }
94540         if (!isDefined(height)) {
94541             height = lastSize.height;
94542             if (height < 0) { //first pass lastComponentSize.height is -Infinity
94543                 height = undef;
94544             }
94545         }
94546         autoWidth = !isNumber(width);
94547         autoHeight = !isNumber(height);
94548
94549         info = {
94550             autoWidth: autoWidth,
94551             autoHeight: autoHeight,
94552             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
94553             height: height,
94554             setOuterWidth: false, //whether the outer el width should be set to the calculated width
94555
94556             // insets for the bodyEl from each side of the component layout area
94557             insets: {
94558                 top: 0,
94559                 right: 0,
94560                 bottom: 0,
94561                 left: 0
94562             }
94563         };
94564
94565         // NOTE the order of calculating insets and setting styles here is very important; we must first
94566         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
94567         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
94568
94569         // perform preparation on the label and error (setting css classes, qtips, etc.)
94570         labelStrategy.prepare(owner, info);
94571         errorStrategy.prepare(owner, info);
94572
94573         // calculate the horizontal insets for the label and error
94574         labelStrategy.adjustHorizInsets(owner, info);
94575         errorStrategy.adjustHorizInsets(owner, info);
94576
94577         // set horizontal styles for label and error based on the current insets
94578         labelStrategy.layoutHoriz(owner, info);
94579         errorStrategy.layoutHoriz(owner, info);
94580
94581         // calculate the vertical insets for the label and error
94582         labelStrategy.adjustVertInsets(owner, info);
94583         errorStrategy.adjustVertInsets(owner, info);
94584
94585         // set vertical styles for label and error based on the current insets
94586         labelStrategy.layoutVert(owner, info);
94587         errorStrategy.layoutVert(owner, info);
94588
94589         // perform sizing of the elements based on the final dimensions and insets
94590         if (autoWidth && autoHeight) {
94591             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
94592             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
94593         } else {
94594             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
94595         }
94596         me.sizeBody(info);
94597
94598         me.activeError = owner.getActiveError();
94599     },
94600     
94601     onFocus: function(){
94602         this.getErrorStrategy().onFocus(this.owner);    
94603     },
94604
94605
94606     /**
94607      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
94608      */
94609     sizeBody: function(info) {
94610         var me = this,
94611             owner = me.owner,
94612             insets = info.insets,
94613             totalWidth = info.width,
94614             totalHeight = info.height,
94615             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
94616             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
94617
94618         // size the bodyEl
94619         me.setElementSize(owner.bodyEl, width, height);
94620
94621         // size the bodyEl's inner contents if necessary
94622         me.sizeBodyContents(width, height);
94623     },
94624
94625     /**
94626      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
94627      * default, subclasses can override to handle their specific contents.
94628      * @param {Number} width The bodyEl width
94629      * @param {Number} height The bodyEl height
94630      */
94631     sizeBodyContents: Ext.emptyFn,
94632
94633
94634     /**
94635      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
94636      * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
94637      */
94638     getLabelStrategy: function() {
94639         var me = this,
94640             strategies = me.labelStrategies,
94641             labelAlign = me.owner.labelAlign;
94642         return strategies[labelAlign] || strategies.base;
94643     },
94644
94645     /**
94646      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
94647      * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
94648      */
94649     getErrorStrategy: function() {
94650         var me = this,
94651             owner = me.owner,
94652             strategies = me.errorStrategies,
94653             msgTarget = owner.msgTarget;
94654         return !owner.preventMark && Ext.isString(msgTarget) ?
94655                 (strategies[msgTarget] || strategies.elementId) :
94656                 strategies.none;
94657     },
94658
94659
94660
94661     /**
94662      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
94663      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
94664      */
94665     labelStrategies: (function() {
94666         var applyIf = Ext.applyIf,
94667             emptyFn = Ext.emptyFn,
94668             base = {
94669                 prepare: function(owner, info) {
94670                     var cls = owner.labelCls + '-' + owner.labelAlign,
94671                         labelEl = owner.labelEl;
94672                     if (labelEl && !labelEl.hasCls(cls)) {
94673                         labelEl.addCls(cls);
94674                     }
94675                 },
94676                 adjustHorizInsets: emptyFn,
94677                 adjustVertInsets: emptyFn,
94678                 layoutHoriz: emptyFn,
94679                 layoutVert: emptyFn
94680             },
94681             left = applyIf({
94682                 prepare: function(owner, info) {
94683                     base.prepare(owner, info);
94684                     // If auto width, add the label width to the body's natural width.
94685                     if (info.autoWidth) {
94686                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
94687                     }
94688                     // Must set outer width to prevent field from wrapping below floated label
94689                     info.setOuterWidth = true;
94690                 },
94691                 adjustHorizInsets: function(owner, info) {
94692                     if (owner.labelEl) {
94693                         info.insets.left += owner.labelWidth + owner.labelPad;
94694                     }
94695                 },
94696                 layoutHoriz: function(owner, info) {
94697                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
94698                     // setting the width style because it needs to account for the final calculated
94699                     // padding/border styles for the label. So we set the width programmatically here to
94700                     // normalize content-box sizing, while letting border-box browsers use the original
94701                     // width style.
94702                     var labelEl = owner.labelEl;
94703                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
94704                         labelEl.setWidth(owner.labelWidth);
94705                         owner.isLabelSized = true;
94706                     }
94707                 }
94708             }, base);
94709
94710
94711         return {
94712             base: base,
94713
94714             /**
94715              * Label displayed above the bodyEl
94716              */
94717             top: applyIf({
94718                 adjustVertInsets: function(owner, info) {
94719                     var labelEl = owner.labelEl;
94720                     if (labelEl) {
94721                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
94722                                            labelEl.getFrameWidth('tb') + owner.labelPad;
94723                     }
94724                 }
94725             }, base),
94726
94727             /**
94728              * Label displayed to the left of the bodyEl
94729              */
94730             left: left,
94731
94732             /**
94733              * Same as left, only difference is text-align in CSS
94734              */
94735             right: left
94736         };
94737     })(),
94738
94739
94740
94741     /**
94742      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
94743      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
94744      */
94745     errorStrategies: (function() {
94746         function setDisplayed(el, displayed) {
94747             var wasDisplayed = el.getStyle('display') !== 'none';
94748             if (displayed !== wasDisplayed) {
94749                 el.setDisplayed(displayed);
94750             }
94751         }
94752
94753         function setStyle(el, name, value) {
94754             if (el.getStyle(name) !== value) {
94755                 el.setStyle(name, value);
94756             }
94757         }
94758         
94759         function showTip(owner) {
94760             var tip = Ext.layout.component.field.Field.tip,
94761                 target;
94762                 
94763             if (tip && tip.isVisible()) {
94764                 target = tip.activeTarget;
94765                 if (target && target.el === owner.getActionEl().dom) {
94766                     tip.toFront(true);
94767                 }
94768             }
94769         }
94770
94771         var applyIf = Ext.applyIf,
94772             emptyFn = Ext.emptyFn,
94773             base = {
94774                 prepare: function(owner) {
94775                     setDisplayed(owner.errorEl, false);
94776                 },
94777                 adjustHorizInsets: emptyFn,
94778                 adjustVertInsets: emptyFn,
94779                 layoutHoriz: emptyFn,
94780                 layoutVert: emptyFn,
94781                 onFocus: emptyFn
94782             };
94783
94784         return {
94785             none: base,
94786
94787             /**
94788              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
94789              */
94790             side: applyIf({
94791                 prepare: function(owner) {
94792                     var errorEl = owner.errorEl;
94793                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
94794                     Ext.layout.component.field.Field.initTip();
94795                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94796                     setDisplayed(errorEl, owner.hasActiveError());
94797                 },
94798                 adjustHorizInsets: function(owner, info) {
94799                     if (owner.autoFitErrors && owner.hasActiveError()) {
94800                         info.insets.right += owner.errorEl.getWidth();
94801                     }
94802                 },
94803                 layoutHoriz: function(owner, info) {
94804                     if (owner.hasActiveError()) {
94805                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
94806                     }
94807                 },
94808                 layoutVert: function(owner, info) {
94809                     if (owner.hasActiveError()) {
94810                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
94811                     }
94812                 },
94813                 onFocus: showTip
94814             }, base),
94815
94816             /**
94817              * Error message displayed underneath the bodyEl
94818              */
94819             under: applyIf({
94820                 prepare: function(owner) {
94821                     var errorEl = owner.errorEl,
94822                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
94823                     if (!errorEl.hasCls(cls)) {
94824                         errorEl.addCls(cls);
94825                     }
94826                     setDisplayed(errorEl, owner.hasActiveError());
94827                 },
94828                 adjustVertInsets: function(owner, info) {
94829                     if (owner.autoFitErrors) {
94830                         info.insets.bottom += owner.errorEl.getHeight();
94831                     }
94832                 },
94833                 layoutHoriz: function(owner, info) {
94834                     var errorEl = owner.errorEl,
94835                         insets = info.insets;
94836
94837                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
94838                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
94839                 }
94840             }, base),
94841
94842             /**
94843              * Error displayed as QuickTip on hover of the field container
94844              */
94845             qtip: applyIf({
94846                 prepare: function(owner) {
94847                     setDisplayed(owner.errorEl, false);
94848                     Ext.layout.component.field.Field.initTip();
94849                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94850                 },
94851                 onFocus: showTip
94852             }, base),
94853
94854             /**
94855              * Error displayed as title tip on hover of the field container
94856              */
94857             title: applyIf({
94858                 prepare: function(owner) {
94859                     setDisplayed(owner.errorEl, false);
94860                     owner.el.dom.title = owner.getActiveError() || '';
94861                 }
94862             }, base),
94863
94864             /**
94865              * Error message displayed as content of an element with a given id elsewhere in the app
94866              */
94867             elementId: applyIf({
94868                 prepare: function(owner) {
94869                     setDisplayed(owner.errorEl, false);
94870                     var targetEl = Ext.fly(owner.msgTarget);
94871                     if (targetEl) {
94872                         targetEl.dom.innerHTML = owner.getActiveError() || '';
94873                         targetEl.setDisplayed(owner.hasActiveError());
94874                     }
94875                 }
94876             }, base)
94877         };
94878     })(),
94879
94880     statics: {
94881         /**
94882          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
94883          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
94884          */
94885         initTip: function() {
94886             var tip = this.tip;
94887             if (!tip) {
94888                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
94889                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
94890                     renderTo: Ext.getBody()
94891                 });
94892                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
94893             }
94894         },
94895
94896         /**
94897          * Destroy the error tip instance.
94898          */
94899         destroyTip: function() {
94900             var tip = this.tip;
94901             if (tip) {
94902                 tip.destroy();
94903                 delete this.tip;
94904             }
94905         }
94906     }
94907
94908 });
94909
94910 /**
94911  * @singleton
94912  * @alternateClassName Ext.form.VTypes
94913  *
94914  * This is a singleton object which contains a set of commonly used field validation functions
94915  * and provides a mechanism for creating reusable custom field validations.
94916  * The following field validation functions are provided out of the box:
94917  *
94918  * - {@link #alpha}
94919  * - {@link #alphanum}
94920  * - {@link #email}
94921  * - {@link #url}
94922  *
94923  * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
94924  *
94925  *     Ext.create('Ext.form.field.Text', {
94926  *         fieldLabel: 'Email Address',
94927  *         name: 'email',
94928  *         vtype: 'email' // applies email validation rules to this field
94929  *     });
94930  *
94931  * To create custom VTypes:
94932  *
94933  *     // custom Vtype for vtype:'time'
94934  *     var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
94935  *     Ext.apply(Ext.form.field.VTypes, {
94936  *         //  vtype validation function
94937  *         time: function(val, field) {
94938  *             return timeTest.test(val);
94939  *         },
94940  *         // vtype Text property: The error text to display when the validation function returns false
94941  *         timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
94942  *         // vtype Mask property: The keystroke filter mask
94943  *         timeMask: /[\d\s:amp]/i
94944  *     });
94945  *
94946  * In the above example the `time` function is the validator that will run when field validation occurs,
94947  * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
94948  * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
94949  *
94950  * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
94951  * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
94952  *
94953  *     Ext.create('Ext.form.field.Text', {
94954  *         fieldLabel: 'Departure Time',
94955  *         name: 'departureTime',
94956  *         vtype: 'time' // applies custom time validation rules to this field
94957  *     });
94958  *
94959  * Another example of a custom validator:
94960  *
94961  *     // custom Vtype for vtype:'IPAddress'
94962  *     Ext.apply(Ext.form.field.VTypes, {
94963  *         IPAddress:  function(v) {
94964  *             return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
94965  *         },
94966  *         IPAddressText: 'Must be a numeric IP address',
94967  *         IPAddressMask: /[\d\.]/i
94968  *     });
94969  *
94970  * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
94971  * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
94972  */
94973 Ext.define('Ext.form.field.VTypes', (function(){
94974     // closure these in so they are only created once.
94975     var alpha = /^[a-zA-Z_]+$/,
94976         alphanum = /^[a-zA-Z0-9_]+$/,
94977         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
94978         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
94979
94980     // All these messages and functions are configurable
94981     return {
94982         singleton: true,
94983         alternateClassName: 'Ext.form.VTypes',
94984
94985         /**
94986          * The function used to validate email addresses. Note that this is a very basic validation - complete
94987          * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
94988          * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
94989          * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
94990          * to validate the following emails:
94991          *
94992          * - `barney@example.de`
94993          * - `barney.rubble@example.com`
94994          * - `barney-rubble@example.coop`
94995          * - `barney+rubble@example.com`
94996          *
94997          * [1]: http://en.wikipedia.org/wiki/E-mail_address
94998          *
94999          * @param {String} value The email address
95000          * @return {Boolean} true if the RegExp test passed, and false if not.
95001          */
95002         'email' : function(v){
95003             return email.test(v);
95004         },
95005         /**
95006          * @property {String} emailText
95007          * The error text to display when the email validation function returns false.
95008          * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
95009          */
95010         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
95011         /**
95012          * @property {RegExp} emailMask
95013          * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
95014          * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
95015          */
95016         'emailMask' : /[a-z0-9_\.\-@\+]/i,
95017
95018         /**
95019          * The function used to validate URLs
95020          * @param {String} value The URL
95021          * @return {Boolean} true if the RegExp test passed, and false if not.
95022          */
95023         'url' : function(v){
95024             return url.test(v);
95025         },
95026         /**
95027          * @property {String} urlText
95028          * The error text to display when the url validation function returns false.
95029          * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
95030          */
95031         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
95032
95033         /**
95034          * The function used to validate alpha values
95035          * @param {String} value The value
95036          * @return {Boolean} true if the RegExp test passed, and false if not.
95037          */
95038         'alpha' : function(v){
95039             return alpha.test(v);
95040         },
95041         /**
95042          * @property {String} alphaText
95043          * The error text to display when the alpha validation function returns false.
95044          * Defaults to: 'This field should only contain letters and _'
95045          */
95046         'alphaText' : 'This field should only contain letters and _',
95047         /**
95048          * @property {RegExp} alphaMask
95049          * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
95050          */
95051         'alphaMask' : /[a-z_]/i,
95052
95053         /**
95054          * The function used to validate alphanumeric values
95055          * @param {String} value The value
95056          * @return {Boolean} true if the RegExp test passed, and false if not.
95057          */
95058         'alphanum' : function(v){
95059             return alphanum.test(v);
95060         },
95061         /**
95062          * @property {String} alphanumText
95063          * The error text to display when the alphanumeric validation function returns false.
95064          * Defaults to: 'This field should only contain letters, numbers and _'
95065          */
95066         'alphanumText' : 'This field should only contain letters, numbers and _',
95067         /**
95068          * @property {RegExp} alphanumMask
95069          * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
95070          */
95071         'alphanumMask' : /[a-z0-9_]/i
95072     };
95073 })());
95074
95075 /**
95076  * @private
95077  * @class Ext.layout.component.field.Text
95078  * @extends Ext.layout.component.field.Field
95079  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
95080  */
95081 Ext.define('Ext.layout.component.field.Text', {
95082     extend: 'Ext.layout.component.field.Field',
95083     alias: 'layout.textfield',
95084     requires: ['Ext.util.TextMetrics'],
95085
95086     type: 'textfield',
95087
95088
95089     /**
95090      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
95091      * changed since the last layout.
95092      */
95093     beforeLayout: function(width, height) {
95094         var me = this,
95095             owner = me.owner,
95096             lastValue = this.lastValue,
95097             value = owner.getRawValue();
95098         this.lastValue = value;
95099         return me.callParent(arguments) || (owner.grow && value !== lastValue);
95100     },
95101
95102
95103     /**
95104      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
95105      * {@link Ext.form.field.Text#grow} configurations.
95106      * @param {Number} width The bodyEl width
95107      * @param {Number} height The bodyEl height
95108      */
95109     sizeBodyContents: function(width, height) {
95110         var size = this.adjustForGrow(width, height);
95111         this.setElementSize(this.owner.inputEl, size[0], size[1]);
95112     },
95113
95114
95115     /**
95116      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
95117      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
95118      * @param {Number} width The bodyEl width
95119      * @param {Number} height The bodyEl height
95120      * @return {Number[]} [inputElWidth, inputElHeight]
95121      */
95122     adjustForGrow: function(width, height) {
95123         var me = this,
95124             owner = me.owner,
95125             inputEl, value, calcWidth,
95126             result = [width, height];
95127
95128         if (owner.grow) {
95129             inputEl = owner.inputEl;
95130
95131             // Find the width that contains the whole text value
95132             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
95133             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
95134
95135             // Constrain
95136             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
95137                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
95138         }
95139
95140         return result;
95141     }
95142
95143 });
95144
95145 /**
95146  * @private
95147  * @class Ext.layout.component.field.TextArea
95148  * @extends Ext.layout.component.field.Field
95149  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
95150  */
95151 Ext.define('Ext.layout.component.field.TextArea', {
95152     extend: 'Ext.layout.component.field.Text',
95153     alias: 'layout.textareafield',
95154
95155     type: 'textareafield',
95156
95157
95158     /**
95159      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
95160      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
95161      * textfield layout's implementation to handle height rather than width.
95162      * @param {Number} width The bodyEl width
95163      * @param {Number} height The bodyEl height
95164      * @return {Number[]} [inputElWidth, inputElHeight]
95165      */
95166     adjustForGrow: function(width, height) {
95167         var me = this,
95168             owner = me.owner,
95169             inputEl, value, max,
95170             curWidth, curHeight, calcHeight,
95171             result = [width, height];
95172
95173         if (owner.grow) {
95174             inputEl = owner.inputEl;
95175             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
95176             curHeight = inputEl.getHeight();
95177
95178             // Get and normalize the field value for measurement
95179             value = inputEl.dom.value || '&#160;';
95180             value += owner.growAppend;
95181
95182             // Translate newlines to <br> tags
95183             value = value.replace(/\n/g, '<br>');
95184
95185             // Find the height that contains the whole text value
95186             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
95187                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
95188
95189             // Constrain
95190             max = owner.growMax;
95191             if (Ext.isNumber(height)) {
95192                 max = Math.min(max, height);
95193             }
95194             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
95195         }
95196
95197         return result;
95198     }
95199
95200 });
95201 /**
95202  * @class Ext.layout.container.Anchor
95203  * @extends Ext.layout.container.Container
95204  * 
95205  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
95206  * If the container is resized, all anchored items are automatically rerendered according to their
95207  * `{@link #anchor}` rules.
95208  *
95209  * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor' 
95210  * config, and should generally not need to be created directly via the new keyword.
95211  * 
95212  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
95213  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
95214  * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
95215  *
95216  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
95217  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
95218  * logic if necessary.  
95219  *
95220  *     @example
95221  *     Ext.create('Ext.Panel', {
95222  *         width: 500,
95223  *         height: 400,
95224  *         title: "AnchorLayout Panel",
95225  *         layout: 'anchor',
95226  *         renderTo: Ext.getBody(),
95227  *         items: [
95228  *             {
95229  *                 xtype: 'panel',
95230  *                 title: '75% Width and 20% Height',
95231  *                 anchor: '75% 20%'
95232  *             },
95233  *             {
95234  *                 xtype: 'panel',
95235  *                 title: 'Offset -300 Width & -200 Height',
95236  *                 anchor: '-300 -200'          
95237  *             },
95238  *             {
95239  *                 xtype: 'panel',
95240  *                 title: 'Mixed Offset and Percent',
95241  *                 anchor: '-250 20%'
95242  *             }
95243  *         ]
95244  *     });
95245  */
95246 Ext.define('Ext.layout.container.Anchor', {
95247
95248     /* Begin Definitions */
95249
95250     alias: 'layout.anchor',
95251     extend: 'Ext.layout.container.Container',
95252     alternateClassName: 'Ext.layout.AnchorLayout',
95253
95254     /* End Definitions */
95255
95256     /**
95257      * @cfg {String} anchor
95258      *
95259      * This configuation option is to be applied to **child `items`** of a container managed by
95260      * this layout (ie. configured with `layout:'anchor'`).
95261      *
95262      * This value is what tells the layout how an item should be anchored to the container. `items`
95263      * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
95264      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
95265      * The following types of anchor values are supported:
95266      *
95267      * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
95268      *
95269      *   The first anchor is the percentage width that the item should take up within the container, and the
95270      *   second is the percentage height.  For example:
95271      *
95272      *       // two values specified
95273      *       anchor: '100% 50%' // render item complete width of the container and
95274      *                          // 1/2 height of the container
95275      *       // one value specified
95276      *       anchor: '100%'     // the width value; the height will default to auto
95277      *
95278      * - **Offsets** : Any positive or negative integer value.
95279      *
95280      *   This is a raw adjustment where the first anchor is the offset from the right edge of the container,
95281      *   and the second is the offset from the bottom edge. For example:
95282      *
95283      *       // two values specified
95284      *       anchor: '-50 -100' // render item the complete width of the container
95285      *                          // minus 50 pixels and
95286      *                          // the complete height minus 100 pixels.
95287      *       // one value specified
95288      *       anchor: '-50'      // anchor value is assumed to be the right offset value
95289      *                          // bottom offset will default to 0
95290      *
95291      * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
95292      *
95293      *   Either the container must have a fixed size or an anchorSize config value defined at render time in
95294      *   order for these to have any effect.
95295      *   
95296      * - **Mixed** :
95297      *
95298      *   Anchor values can also be mixed as needed.  For example, to render the width offset from the container
95299      *   right edge by 50 pixels and 75% of the container's height use:
95300      *   
95301      *       anchor:   '-50 75%'
95302      */
95303     type: 'anchor',
95304
95305     /**
95306      * @cfg {String} defaultAnchor
95307      * Default anchor for all child <b>container</b> items applied if no anchor or specific width is set on the child item.  Defaults to '100%'.
95308      */
95309     defaultAnchor: '100%',
95310
95311     parseAnchorRE: /^(r|right|b|bottom)$/i,
95312
95313     // private
95314     onLayout: function() {
95315         this.callParent(arguments);
95316
95317         var me = this,
95318             size = me.getLayoutTargetSize(),
95319             owner = me.owner,
95320             target = me.getTarget(),
95321             ownerWidth = size.width,
95322             ownerHeight = size.height,
95323             overflow = target.getStyle('overflow'),
95324             components = me.getVisibleItems(owner),
95325             len = components.length,
95326             boxes = [],
95327             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
95328             i, el, cleaner;
95329
95330         if (ownerWidth < 20 && ownerHeight < 20) {
95331             return;
95332         }
95333
95334         // Anchor layout uses natural HTML flow to arrange the child items.
95335         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
95336         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
95337         if (!me.clearEl) {
95338             me.clearEl = target.createChild({
95339                 cls: Ext.baseCSSPrefix + 'clear',
95340                 role: 'presentation'
95341             });
95342         }
95343
95344         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
95345         if (!Ext.supports.RightMargin) {
95346             cleaner = Ext.Element.getRightMarginFixCleaner(target);
95347             target.addCls(Ext.baseCSSPrefix + 'inline-children');
95348         }
95349
95350         for (i = 0; i < len; i++) {
95351             component = components[i];
95352             el = component.el;
95353
95354             anchorSpec = component.anchorSpec;
95355             if (anchorSpec) {
95356                 if (anchorSpec.right) {
95357                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
95358                 } else {
95359                     calcWidth = undefined;
95360                 }
95361                 if (anchorSpec.bottom) {
95362                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
95363                 } else {
95364                     calcHeight = undefined;
95365                 }
95366
95367                 boxes.push({
95368                     component: component,
95369                     anchor: true,
95370                     width: calcWidth || undefined,
95371                     height: calcHeight || undefined
95372                 });
95373             } else {
95374                 boxes.push({
95375                     component: component,
95376                     anchor: false
95377                 });
95378             }
95379         }
95380
95381         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
95382         if (!Ext.supports.RightMargin) {
95383             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
95384             cleaner();
95385         }
95386
95387         for (i = 0; i < len; i++) {
95388             box = boxes[i];
95389             me.setItemSize(box.component, box.width, box.height);
95390         }
95391
95392         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
95393             newTargetSize = me.getLayoutTargetSize();
95394             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
95395                 me.adjustmentPass = true;
95396                 me.onLayout();
95397             }
95398         }
95399
95400         delete me.adjustmentPass;
95401     },
95402
95403     // private
95404     parseAnchor: function(a, start, cstart) {
95405         if (a && a != 'none') {
95406             var ratio;
95407             // standard anchor
95408             if (this.parseAnchorRE.test(a)) {
95409                 var diff = cstart - start;
95410                 return function(v) {
95411                     return v - diff;
95412                 };
95413             }    
95414             // percentage
95415             else if (a.indexOf('%') != -1) {
95416                 ratio = parseFloat(a.replace('%', '')) * 0.01;
95417                 return function(v) {
95418                     return Math.floor(v * ratio);
95419                 };
95420             }    
95421             // simple offset adjustment
95422             else {
95423                 a = parseInt(a, 10);
95424                 if (!isNaN(a)) {
95425                     return function(v) {
95426                         return v + a;
95427                     };
95428                 }
95429             }
95430         }
95431         return null;
95432     },
95433
95434     // private
95435     adjustWidthAnchor: function(value, comp) {
95436         return value;
95437     },
95438
95439     // private
95440     adjustHeightAnchor: function(value, comp) {
95441         return value;
95442     },
95443
95444     configureItem: function(item) {
95445         var me = this,
95446             owner = me.owner,
95447             anchor= item.anchor,
95448             anchorsArray,
95449             anchorSpec,
95450             anchorWidth,
95451             anchorHeight;
95452
95453         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
95454             item.anchor = anchor = me.defaultAnchor;
95455         }
95456
95457         // find the container anchoring size
95458         if (owner.anchorSize) {
95459             if (typeof owner.anchorSize == 'number') {
95460                 anchorWidth = owner.anchorSize;
95461             }
95462             else {
95463                 anchorWidth = owner.anchorSize.width;
95464                 anchorHeight = owner.anchorSize.height;
95465             }
95466         }
95467         else {
95468             anchorWidth = owner.initialConfig.width;
95469             anchorHeight = owner.initialConfig.height;
95470         }
95471
95472         if (anchor) {
95473             // cache all anchor values
95474             anchorsArray = anchor.split(' ');
95475             item.anchorSpec = anchorSpec = {
95476                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
95477                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
95478             };
95479
95480             if (anchorSpec.right) {
95481                 item.layoutManagedWidth = 1;
95482             } else {
95483                 item.layoutManagedWidth = 2;
95484             }
95485
95486             if (anchorSpec.bottom) {
95487                 item.layoutManagedHeight = 1;
95488             } else {
95489                 item.layoutManagedHeight = 2;
95490             }
95491         } else {
95492             item.layoutManagedWidth = 2;
95493             item.layoutManagedHeight = 2;
95494         }
95495         this.callParent(arguments);
95496     }
95497
95498 });
95499 /**
95500  * @class Ext.form.action.Load
95501  * @extends Ext.form.action.Action
95502  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
95503  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
95504  * {@link Ext.form.Basic#load load}ing.</p>
95505  * <p><u><b>Response Packet Criteria</b></u></p>
95506  * <p>A response packet <b>must</b> contain:
95507  * <div class="mdetail-params"><ul>
95508  * <li><b><code>success</code></b> property : Boolean</li>
95509  * <li><b><code>data</code></b> property : Object</li>
95510  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
95511  * The individual value object for each Field is passed to the Field's
95512  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
95513  * </ul></div>
95514  * <p><u><b>JSON Packets</b></u></p>
95515  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
95516 var myFormPanel = new Ext.form.Panel({
95517     title: 'Client and routing info',
95518     items: [{
95519         fieldLabel: 'Client',
95520         name: 'clientName'
95521     }, {
95522         fieldLabel: 'Port of loading',
95523         name: 'portOfLoading'
95524     }, {
95525         fieldLabel: 'Port of discharge',
95526         name: 'portOfDischarge'
95527     }]
95528 });
95529 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
95530     url: '/getRoutingInfo.php',
95531     params: {
95532         consignmentRef: myConsignmentRef
95533     },
95534     failure: function(form, action) {
95535         Ext.Msg.alert("Load failed", action.result.errorMessage);
95536     }
95537 });
95538 </code></pre>
95539  * a <b>success response</b> packet may look like this:</p><pre><code>
95540 {
95541     success: true,
95542     data: {
95543         clientName: "Fred. Olsen Lines",
95544         portOfLoading: "FXT",
95545         portOfDischarge: "OSL"
95546     }
95547 }</code></pre>
95548  * while a <b>failure response</b> packet may look like this:</p><pre><code>
95549 {
95550     success: false,
95551     errorMessage: "Consignment reference not found"
95552 }</code></pre>
95553  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
95554  * callback or event handler methods. The object decoded from this JSON is available in the
95555  * {@link Ext.form.action.Action#result result} property.</p>
95556  */
95557 Ext.define('Ext.form.action.Load', {
95558     extend:'Ext.form.action.Action',
95559     requires: ['Ext.data.Connection'],
95560     alternateClassName: 'Ext.form.Action.Load',
95561     alias: 'formaction.load',
95562
95563     type: 'load',
95564
95565     /**
95566      * @private
95567      */
95568     run: function() {
95569         Ext.Ajax.request(Ext.apply(
95570             this.createCallback(),
95571             {
95572                 method: this.getMethod(),
95573                 url: this.getUrl(),
95574                 headers: this.headers,
95575                 params: this.getParams()
95576             }
95577         ));
95578     },
95579
95580     /**
95581      * @private
95582      */
95583     onSuccess: function(response){
95584         var result = this.processResponse(response),
95585             form = this.form;
95586         if (result === true || !result.success || !result.data) {
95587             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
95588             form.afterAction(this, false);
95589             return;
95590         }
95591         form.clearInvalid();
95592         form.setValues(result.data);
95593         form.afterAction(this, true);
95594     },
95595
95596     /**
95597      * @private
95598      */
95599     handleResponse: function(response) {
95600         var reader = this.form.reader,
95601             rs, data;
95602         if (reader) {
95603             rs = reader.read(response);
95604             data = rs.records && rs.records[0] ? rs.records[0].data : null;
95605             return {
95606                 success : rs.success,
95607                 data : data
95608             };
95609         }
95610         return Ext.decode(response.responseText);
95611     }
95612 });
95613
95614
95615 /**
95616  * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
95617  * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
95618  * their prior size, and can be {@link #minimize}d.
95619  *
95620  * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
95621  * grouping, activation, to front, to back and other application-specific behavior.
95622  *
95623  * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
95624  * {@link Ext.Component#renderTo renderTo}.
95625  *
95626  * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
95627  * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
95628  * in the required manner.**
95629  *
95630  *     @example
95631  *     Ext.create('Ext.window.Window', {
95632  *         title: 'Hello',
95633  *         height: 200,
95634  *         width: 400,
95635  *         layout: 'fit',
95636  *         items: {  // Let's put an empty grid in just to illustrate fit layout
95637  *             xtype: 'grid',
95638  *             border: false,
95639  *             columns: [{header: 'World'}],                 // One header just for show. There's no data,
95640  *             store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
95641  *         }
95642  *     }).show();
95643  */
95644 Ext.define('Ext.window.Window', {
95645     extend: 'Ext.panel.Panel',
95646
95647     alternateClassName: 'Ext.Window',
95648
95649     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
95650
95651     alias: 'widget.window',
95652
95653     /**
95654      * @cfg {Number} x
95655      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
95656      * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
95657      */
95658
95659     /**
95660      * @cfg {Number} y
95661      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
95662      * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
95663      */
95664
95665     /**
95666      * @cfg {Boolean} [modal=false]
95667      * True to make the window modal and mask everything behind it when displayed, false to display it without
95668      * restricting access to other UI elements.
95669      */
95670
95671     /**
95672      * @cfg {String/Ext.Element} [animateTarget=null]
95673      * Id or element from which the window should animate while opening.
95674      */
95675
95676     /**
95677      * @cfg {String/Number/Ext.Component} defaultFocus
95678      * Specifies a Component to receive focus when this Window is focused.
95679      *
95680      * This may be one of:
95681      *
95682      *   - The index of a footer Button.
95683      *   - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
95684      *   - A Component.
95685      */
95686
95687     /**
95688      * @cfg {Function} onEsc
95689      * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
95690      * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
95691      * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
95692      */
95693
95694     /**
95695      * @cfg {Boolean} [collapsed=false]
95696      * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
95697      * is true (the default) it will override the `collapsed` config and the window will always be
95698      * expanded when shown.
95699      */
95700
95701     /**
95702      * @cfg {Boolean} [maximized=false]
95703      * True to initially display the window in a maximized state.
95704      */
95705
95706     /**
95707     * @cfg {String} [baseCls='x-window']
95708     * The base CSS class to apply to this panel's element.
95709     */
95710     baseCls: Ext.baseCSSPrefix + 'window',
95711
95712     /**
95713      * @cfg {Boolean/Object} resizable
95714      * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
95715      *
95716      * This may also be specified as a config object to Ext.resizer.Resizer
95717      */
95718     resizable: true,
95719
95720     /**
95721      * @cfg {Boolean} draggable
95722      * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
95723      * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
95724      * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
95725      */
95726     draggable: true,
95727
95728     /**
95729      * @cfg {Boolean} constrain
95730      * True to constrain the window within its containing element, false to allow it to fall outside of its containing
95731      * element. By default the window will be rendered to document.body. To render and constrain the window within
95732      * another element specify {@link #renderTo}. Optionally the header only can be constrained
95733      * using {@link #constrainHeader}.
95734      */
95735     constrain: false,
95736
95737     /**
95738      * @cfg {Boolean} constrainHeader
95739      * True to constrain the window header within its containing element (allowing the window body to fall outside of
95740      * its containing element) or false to allow the header to fall outside its containing element.
95741      * Optionally the entire window can be constrained using {@link #constrain}.
95742      */
95743     constrainHeader: false,
95744
95745     /**
95746      * @cfg {Boolean} plain
95747      * True to render the window body with a transparent background so that it will blend into the framing elements,
95748      * false to add a lighter background color to visually highlight the body element and separate it more distinctly
95749      * from the surrounding frame.
95750      */
95751     plain: false,
95752
95753     /**
95754      * @cfg {Boolean} minimizable
95755      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
95756      * and disallow minimizing the window. Note that this button provides no implementation -- the
95757      * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
95758      * minimize behavior implemented for this option to be useful.
95759      */
95760     minimizable: false,
95761
95762     /**
95763      * @cfg {Boolean} maximizable
95764      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
95765      * and disallow maximizing the window. Note that when a window is maximized, the tool button
95766      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
95767      * the window to its previous size.
95768      */
95769     maximizable: false,
95770
95771     // inherit docs
95772     minHeight: 100,
95773
95774     // inherit docs
95775     minWidth: 200,
95776
95777     /**
95778      * @cfg {Boolean} expandOnShow
95779      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
95780      * {@link #collapsed}) when displayed.
95781      */
95782     expandOnShow: true,
95783
95784     // inherited docs, same default
95785     collapsible: false,
95786
95787     /**
95788      * @cfg {Boolean} closable
95789      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
95790      * disallow closing the window.
95791      *
95792      * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
95793      * Window has focus, the {@link #close} method will be called. This will _{@link Ext.Component#destroy destroy}_ the
95794      * Window and its content meaning that it may not be reused.
95795      *
95796      * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
95797      */
95798     closable: true,
95799
95800     /**
95801      * @cfg {Boolean} hidden
95802      * Render this Window hidden. If `true`, the {@link #hide} method will be called internally.
95803      */
95804     hidden: true,
95805
95806     // Inherit docs from Component. Windows render to the body on first show.
95807     autoRender: true,
95808
95809     // Inherit docs from Component. Windows hide using visibility.
95810     hideMode: 'visibility',
95811
95812     /** @cfg {Boolean} floating @hide Windows are always floating*/
95813     floating: true,
95814
95815     ariaRole: 'alertdialog',
95816
95817     itemCls: 'x-window-item',
95818
95819     overlapHeader: true,
95820
95821     ignoreHeaderBorderManagement: true,
95822
95823     // private
95824     initComponent: function() {
95825         var me = this;
95826         me.callParent();
95827         me.addEvents(
95828             /**
95829              * @event activate
95830              * Fires after the window has been visually activated via {@link #setActive}.
95831              * @param {Ext.window.Window} this
95832              */
95833
95834             /**
95835              * @event deactivate
95836              * Fires after the window has been visually deactivated via {@link #setActive}.
95837              * @param {Ext.window.Window} this
95838              */
95839
95840             /**
95841              * @event resize
95842              * Fires after the window has been resized.
95843              * @param {Ext.window.Window} this
95844              * @param {Number} width The window's new width
95845              * @param {Number} height The window's new height
95846              */
95847             'resize',
95848
95849             /**
95850              * @event maximize
95851              * Fires after the window has been maximized.
95852              * @param {Ext.window.Window} this
95853              */
95854             'maximize',
95855
95856             /**
95857              * @event minimize
95858              * Fires after the window has been minimized.
95859              * @param {Ext.window.Window} this
95860              */
95861             'minimize',
95862
95863             /**
95864              * @event restore
95865              * Fires after the window has been restored to its original size after being maximized.
95866              * @param {Ext.window.Window} this
95867              */
95868             'restore'
95869         );
95870
95871         if (me.plain) {
95872             me.addClsWithUI('plain');
95873         }
95874
95875         if (me.modal) {
95876             me.ariaRole = 'dialog';
95877         }
95878     },
95879
95880     // State Management
95881     // private
95882
95883     initStateEvents: function(){
95884         var events = this.stateEvents;
95885         // push on stateEvents if they don't exist
95886         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
95887             if (Ext.Array.indexOf(events, event)) {
95888                 events.push(event);
95889             }
95890         });
95891         this.callParent();
95892     },
95893
95894     getState: function() {
95895         var me = this,
95896             state = me.callParent() || {},
95897             maximized = !!me.maximized;
95898
95899         state.maximized = maximized;
95900         Ext.apply(state, {
95901             size: maximized ? me.restoreSize : me.getSize(),
95902             pos: maximized ? me.restorePos : me.getPosition()
95903         });
95904         return state;
95905     },
95906
95907     applyState: function(state){
95908         var me = this;
95909
95910         if (state) {
95911             me.maximized = state.maximized;
95912             if (me.maximized) {
95913                 me.hasSavedRestore = true;
95914                 me.restoreSize = state.size;
95915                 me.restorePos = state.pos;
95916             } else {
95917                 Ext.apply(me, {
95918                     width: state.size.width,
95919                     height: state.size.height,
95920                     x: state.pos[0],
95921                     y: state.pos[1]
95922                 });
95923             }
95924         }
95925     },
95926
95927     // private
95928     onMouseDown: function (e) {
95929         var preventFocus;
95930             
95931         if (this.floating) {
95932             if (Ext.fly(e.getTarget()).focusable()) {
95933                 preventFocus = true;
95934             }
95935             this.toFront(preventFocus);
95936         }
95937     },
95938
95939     // private
95940     onRender: function(ct, position) {
95941         var me = this;
95942         me.callParent(arguments);
95943         me.focusEl = me.el;
95944
95945         // Double clicking a header will toggleMaximize
95946         if (me.maximizable) {
95947             me.header.on({
95948                 dblclick: {
95949                     fn: me.toggleMaximize,
95950                     element: 'el',
95951                     scope: me
95952                 }
95953             });
95954         }
95955     },
95956
95957     // private
95958     afterRender: function() {
95959         var me = this,
95960             hidden = me.hidden,
95961             keyMap;
95962
95963         me.hidden = false;
95964         // Component's afterRender sizes and positions the Component
95965         me.callParent();
95966         me.hidden = hidden;
95967
95968         // Create the proxy after the size has been applied in Component.afterRender
95969         me.proxy = me.getProxy();
95970
95971         // clickToRaise
95972         me.mon(me.el, 'mousedown', me.onMouseDown, me);
95973         
95974         // allow the element to be focusable
95975         me.el.set({
95976             tabIndex: -1
95977         });
95978
95979         // Initialize
95980         if (me.maximized) {
95981             me.maximized = false;
95982             me.maximize();
95983         }
95984
95985         if (me.closable) {
95986             keyMap = me.getKeyMap();
95987             keyMap.on(27, me.onEsc, me);
95988
95989             //if (hidden) { ? would be consistent w/before/afterShow...
95990                 keyMap.disable();
95991             //}
95992         }
95993
95994         if (!hidden) {
95995             me.syncMonitorWindowResize();
95996             me.doConstrain();
95997         }
95998     },
95999
96000     /**
96001      * @private
96002      * @override
96003      * Override Component.initDraggable.
96004      * Window uses the header element as the delegate.
96005      */
96006     initDraggable: function() {
96007         var me = this,
96008             ddConfig;
96009
96010         if (!me.header) {
96011             me.updateHeader(true);
96012         }
96013
96014         /*
96015          * Check the header here again. If for whatever reason it wasn't created in
96016          * updateHeader (preventHeader) then we'll just ignore the rest since the
96017          * header acts as the drag handle.
96018          */
96019         if (me.header) {
96020             ddConfig = Ext.applyIf({
96021                 el: me.el,
96022                 delegate: '#' + me.header.id
96023             }, me.draggable);
96024
96025             // Add extra configs if Window is specified to be constrained
96026             if (me.constrain || me.constrainHeader) {
96027                 ddConfig.constrain = me.constrain;
96028                 ddConfig.constrainDelegate = me.constrainHeader;
96029                 ddConfig.constrainTo = me.constrainTo || me.container;
96030             }
96031
96032             /**
96033              * @property {Ext.util.ComponentDragger} dd
96034              * If this Window is configured {@link #draggable}, this property will contain an instance of
96035              * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
96036              * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
96037              *
96038              * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
96039              * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
96040              * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
96041              */
96042             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
96043             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
96044         }
96045     },
96046
96047     // private
96048     onEsc: function(k, e) {
96049         e.stopEvent();
96050         this[this.closeAction]();
96051     },
96052
96053     // private
96054     beforeDestroy: function() {
96055         var me = this;
96056         if (me.rendered) {
96057             delete this.animateTarget;
96058             me.hide();
96059             Ext.destroy(
96060                 me.keyMap
96061             );
96062         }
96063         me.callParent();
96064     },
96065
96066     /**
96067      * @private
96068      * @override
96069      * Contribute class-specific tools to the header.
96070      * Called by Panel's initTools.
96071      */
96072     addTools: function() {
96073         var me = this;
96074
96075         // Call Panel's initTools
96076         me.callParent();
96077
96078         if (me.minimizable) {
96079             me.addTool({
96080                 type: 'minimize',
96081                 handler: Ext.Function.bind(me.minimize, me, [])
96082             });
96083         }
96084         if (me.maximizable) {
96085             me.addTool({
96086                 type: 'maximize',
96087                 handler: Ext.Function.bind(me.maximize, me, [])
96088             });
96089             me.addTool({
96090                 type: 'restore',
96091                 handler: Ext.Function.bind(me.restore, me, []),
96092                 hidden: true
96093             });
96094         }
96095     },
96096
96097     /**
96098      * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
96099      * Container itself will receive focus.
96100      */
96101     getFocusEl: function() {
96102         var me = this,
96103             f = me.focusEl,
96104             defaultComp = me.defaultButton || me.defaultFocus,
96105             t = typeof db,
96106             el,
96107             ct;
96108
96109         if (Ext.isDefined(defaultComp)) {
96110             if (Ext.isNumber(defaultComp)) {
96111                 f = me.query('button')[defaultComp];
96112             } else if (Ext.isString(defaultComp)) {
96113                 f = me.down('#' + defaultComp);
96114             } else {
96115                 f = defaultComp;
96116             }
96117         }
96118         return f || me.focusEl;
96119     },
96120
96121     // private
96122     beforeShow: function() {
96123         this.callParent();
96124
96125         if (this.expandOnShow) {
96126             this.expand(false);
96127         }
96128     },
96129
96130     // private
96131     afterShow: function(animateTarget) {
96132         var me = this,
96133             animating = animateTarget || me.animateTarget;
96134
96135
96136         // No constraining code needs to go here.
96137         // Component.onShow constrains the Component. *If the constrain config is true*
96138
96139         // Perform superclass's afterShow tasks
96140         // Which might include animating a proxy from an animateTarget
96141         me.callParent(arguments);
96142
96143         if (me.maximized) {
96144             me.fitContainer();
96145         }
96146
96147         me.syncMonitorWindowResize();
96148         if (!animating) {
96149             me.doConstrain();
96150         }
96151
96152         if (me.keyMap) {
96153             me.keyMap.enable();
96154         }
96155     },
96156
96157     // private
96158     doClose: function() {
96159         var me = this;
96160
96161         // Being called as callback after going through the hide call below
96162         if (me.hidden) {
96163             me.fireEvent('close', me);
96164             if (me.closeAction == 'destroy') {
96165                 this.destroy();
96166             }
96167         } else {
96168             // close after hiding
96169             me.hide(me.animateTarget, me.doClose, me);
96170         }
96171     },
96172
96173     // private
96174     afterHide: function() {
96175         var me = this;
96176
96177         // No longer subscribe to resizing now that we're hidden
96178         me.syncMonitorWindowResize();
96179
96180         // Turn off keyboard handling once window is hidden
96181         if (me.keyMap) {
96182             me.keyMap.disable();
96183         }
96184
96185         // Perform superclass's afterHide tasks.
96186         me.callParent(arguments);
96187     },
96188
96189     // private
96190     onWindowResize: function() {
96191         if (this.maximized) {
96192             this.fitContainer();
96193         }
96194         this.doConstrain();
96195     },
96196
96197     /**
96198      * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
96199      * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
96200      * the minimize event can be handled or this method can be overridden.
96201      * @return {Ext.window.Window} this
96202      */
96203     minimize: function() {
96204         this.fireEvent('minimize', this);
96205         return this;
96206     },
96207
96208     afterCollapse: function() {
96209         var me = this;
96210
96211         if (me.maximizable) {
96212             me.tools.maximize.hide();
96213             me.tools.restore.hide();
96214         }
96215         if (me.resizer) {
96216             me.resizer.disable();
96217         }
96218         me.callParent(arguments);
96219     },
96220
96221     afterExpand: function() {
96222         var me = this;
96223
96224         if (me.maximized) {
96225             me.tools.restore.show();
96226         } else if (me.maximizable) {
96227             me.tools.maximize.show();
96228         }
96229         if (me.resizer) {
96230             me.resizer.enable();
96231         }
96232         me.callParent(arguments);
96233     },
96234
96235     /**
96236      * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
96237      * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
96238      * @return {Ext.window.Window} this
96239      */
96240     maximize: function() {
96241         var me = this;
96242
96243         if (!me.maximized) {
96244             me.expand(false);
96245             if (!me.hasSavedRestore) {
96246                 me.restoreSize = me.getSize();
96247                 me.restorePos = me.getPosition(true);
96248             }
96249             if (me.maximizable) {
96250                 me.tools.maximize.hide();
96251                 me.tools.restore.show();
96252             }
96253             me.maximized = true;
96254             me.el.disableShadow();
96255
96256             if (me.dd) {
96257                 me.dd.disable();
96258             }
96259             if (me.collapseTool) {
96260                 me.collapseTool.hide();
96261             }
96262             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
96263             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
96264
96265             me.syncMonitorWindowResize();
96266             me.setPosition(0, 0);
96267             me.fitContainer();
96268             me.fireEvent('maximize', me);
96269         }
96270         return me;
96271     },
96272
96273     /**
96274      * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
96275      * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
96276      * @return {Ext.window.Window} this
96277      */
96278     restore: function() {
96279         var me = this,
96280             tools = me.tools;
96281
96282         if (me.maximized) {
96283             delete me.hasSavedRestore;
96284             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
96285
96286             // Toggle tool visibility
96287             if (tools.restore) {
96288                 tools.restore.hide();
96289             }
96290             if (tools.maximize) {
96291                 tools.maximize.show();
96292             }
96293             if (me.collapseTool) {
96294                 me.collapseTool.show();
96295             }
96296
96297             // Restore the position/sizing
96298             me.setPosition(me.restorePos);
96299             me.setSize(me.restoreSize);
96300
96301             // Unset old position/sizing
96302             delete me.restorePos;
96303             delete me.restoreSize;
96304
96305             me.maximized = false;
96306
96307             me.el.enableShadow(true);
96308
96309             // Allow users to drag and drop again
96310             if (me.dd) {
96311                 me.dd.enable();
96312             }
96313
96314             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
96315
96316             me.syncMonitorWindowResize();
96317             me.doConstrain();
96318             me.fireEvent('restore', me);
96319         }
96320         return me;
96321     },
96322
96323     /**
96324      * Synchronizes the presence of our listener for window resize events. This method
96325      * should be called whenever this status might change.
96326      * @private
96327      */
96328     syncMonitorWindowResize: function () {
96329         var me = this,
96330             currentlyMonitoring = me._monitoringResize,
96331             // all the states where we should be listening to window resize:
96332             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
96333             // all the states where we veto this:
96334             veto = me.hidden || me.destroying || me.isDestroyed;
96335
96336         if (yes && !veto) {
96337             // we should be listening...
96338             if (!currentlyMonitoring) {
96339                 // but we aren't, so set it up
96340                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
96341                 me._monitoringResize = true;
96342             }
96343         } else if (currentlyMonitoring) {
96344             // we should not be listening, but we are, so tear it down
96345             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
96346             me._monitoringResize = false;
96347         }
96348     },
96349
96350     /**
96351      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
96352      * state of the window.
96353      * @return {Ext.window.Window} this
96354      */
96355     toggleMaximize: function() {
96356         return this[this.maximized ? 'restore': 'maximize']();
96357     }
96358
96359     /**
96360      * @cfg {Boolean} autoWidth @hide
96361      * Absolute positioned element and therefore cannot support autoWidth.
96362      * A width is a required configuration.
96363      **/
96364 });
96365
96366 /**
96367  * @docauthor Jason Johnston <jason@sencha.com>
96368  *
96369  * Base class for form fields that provides default event handling, rendering, and other common functionality
96370  * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
96371  * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
96372  *
96373  * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
96374  * rather than creating instances of this class directly. However if you are implementing a custom form field,
96375  * using this as the parent class is recommended.
96376  *
96377  * # Values and Conversions
96378  *
96379  * Because BaseField implements the Field mixin, it has a main value that can be initialized with the
96380  * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
96381  * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
96382  * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
96383  * input, this value data type can not always be directly used in the rendered field.
96384  *
96385  * Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
96386  * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
96387  * work with the raw value, though it is recommended to use getValue and setValue in most cases.
96388  *
96389  * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
96390  * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
96391  * should override these methods to handle the conversion.
96392  *
96393  * # Rendering
96394  *
96395  * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
96396  * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
96397  * field renderings.
96398  *
96399  * # Example usage:
96400  *
96401  *     @example
96402  *     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
96403  *     // searchUrl when the Enter key is pressed.222
96404  *     Ext.define('Ext.form.SearchField', {
96405  *         extend: 'Ext.form.field.Base',
96406  *         alias: 'widget.searchfield',
96407  *     
96408  *         inputType: 'search',
96409  *     
96410  *         // Config defining the search URL
96411  *         searchUrl: 'http://www.google.com/search?q={0}',
96412  *     
96413  *         // Add specialkey listener
96414  *         initComponent: function() {
96415  *             this.callParent();
96416  *             this.on('specialkey', this.checkEnterKey, this);
96417  *         },
96418  *     
96419  *         // Handle enter key presses, execute the search if the field has a value
96420  *         checkEnterKey: function(field, e) {
96421  *             var value = this.getValue();
96422  *             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
96423  *                 location.href = Ext.String.format(this.searchUrl, value);
96424  *             }
96425  *         }
96426  *     });
96427  *     
96428  *     Ext.create('Ext.form.Panel', {
96429  *         title: 'BaseField Example',
96430  *         bodyPadding: 5,
96431  *         width: 250,
96432  *     
96433  *         // Fields will be arranged vertically, stretched to full width
96434  *         layout: 'anchor',
96435  *         defaults: {
96436  *             anchor: '100%'
96437  *         },
96438  *         items: [{
96439  *             xtype: 'searchfield',
96440  *             fieldLabel: 'Search',
96441  *             name: 'query'
96442  *         }],
96443  *         renderTo: Ext.getBody()
96444  *     });
96445  */
96446 Ext.define('Ext.form.field.Base', {
96447     extend: 'Ext.Component',
96448     mixins: {
96449         labelable: 'Ext.form.Labelable',
96450         field: 'Ext.form.field.Field'
96451     },
96452     alias: 'widget.field',
96453     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
96454     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
96455
96456     /**
96457      * @cfg {Ext.XTemplate} fieldSubTpl
96458      * The content of the field body is defined by this config option.
96459      */
96460     fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
96461         '<input id="{id}" type="{type}" ',
96462         '<tpl if="name">name="{name}" </tpl>',
96463         '<tpl if="size">size="{size}" </tpl>',
96464         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
96465         'class="{fieldCls} {typeCls}" autocomplete="off" />',
96466         {
96467             compiled: true,
96468             disableFormats: true
96469         }
96470     ],
96471
96472     /**
96473      * @cfg {String} name
96474      * The name of the field. This is used as the parameter name when including the field value
96475      * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
96476      * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
96477      */
96478
96479     /**
96480      * @cfg {String} inputType
96481      * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
96482      * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
96483      * fall back to 'text'.
96484      *
96485      * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
96486      * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
96487      * a plain unstyled file input you can use a BaseField with inputType:'file'.
96488      */
96489     inputType: 'text',
96490
96491     /**
96492      * @cfg {Number} tabIndex
96493      * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
96494      * applyTo
96495      */
96496
96497     /**
96498      * @cfg {String} invalidText
96499      * The error text to use when marking a field invalid and no message is provided
96500      */
96501     invalidText : 'The value in this field is invalid',
96502
96503     /**
96504      * @cfg {String} [fieldCls='x-form-field']
96505      * The default CSS class for the field input
96506      */
96507     fieldCls : Ext.baseCSSPrefix + 'form-field',
96508
96509     /**
96510      * @cfg {String} fieldStyle
96511      * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
96512      * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
96513      * the style after initialization.
96514      */
96515
96516     /**
96517      * @cfg {String} [focusCls='x-form-focus']
96518      * The CSS class to use when the field receives focus
96519      */
96520     focusCls : Ext.baseCSSPrefix + 'form-focus',
96521
96522     /**
96523      * @cfg {String} dirtyCls
96524      * The CSS class to use when the field value {@link #isDirty is dirty}.
96525      */
96526     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
96527
96528     /**
96529      * @cfg {String[]} checkChangeEvents
96530      * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
96531      * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
96532      * fired, followed by validation if the {@link #validateOnChange} option is enabled.
96533      *
96534      * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
96535      * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
96536      * browsers; the only known exceptions at the time of writing are:
96537      *
96538      *   - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
96539      *   - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
96540      *     and textareas
96541      *   - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
96542      *
96543      * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
96544      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
96545      * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
96546      * such a task automatically.
96547      */
96548     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
96549                         ['change', 'propertychange'] :
96550                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
96551
96552     /**
96553      * @cfg {Number} checkChangeBuffer
96554      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
96555      * Defaults to 50 milliseconds.
96556      */
96557     checkChangeBuffer: 50,
96558
96559     componentLayout: 'field',
96560
96561     /**
96562      * @cfg {Boolean} readOnly
96563      * true to mark the field as readOnly in HTML.
96564      *
96565      * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
96566      * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
96567      * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
96568      */
96569     readOnly : false,
96570
96571     /**
96572      * @cfg {String} readOnlyCls
96573      * The CSS class applied to the component's main element when it is {@link #readOnly}.
96574      */
96575     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
96576
96577     /**
96578      * @cfg {String} inputId
96579      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
96580      * configure this manually, you must make sure it is unique in the document.
96581      */
96582
96583     /**
96584      * @cfg {Boolean} validateOnBlur
96585      * Whether the field should validate when it loses focus. This will cause fields to be validated
96586      * as the user steps through the fields in the form regardless of whether they are making changes to those fields
96587      * along the way. See also {@link #validateOnChange}.
96588      */
96589     validateOnBlur: true,
96590
96591     // private
96592     hasFocus : false,
96593
96594     baseCls: Ext.baseCSSPrefix + 'field',
96595
96596     maskOnDisable: false,
96597
96598     // private
96599     initComponent : function() {
96600         var me = this;
96601
96602         me.callParent();
96603
96604         me.subTplData = me.subTplData || {};
96605
96606         me.addEvents(
96607             /**
96608              * @event focus
96609              * Fires when this field receives input focus.
96610              * @param {Ext.form.field.Base} this
96611              */
96612             'focus',
96613             /**
96614              * @event blur
96615              * Fires when this field loses input focus.
96616              * @param {Ext.form.field.Base} this
96617              */
96618             'blur',
96619             /**
96620              * @event specialkey
96621              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
96622              * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
96623              * pressed. For example:
96624              *
96625              *     var form = new Ext.form.Panel({
96626              *         ...
96627              *         items: [{
96628              *                 fieldLabel: 'Field 1',
96629              *                 name: 'field1',
96630              *                 allowBlank: false
96631              *             },{
96632              *                 fieldLabel: 'Field 2',
96633              *                 name: 'field2',
96634              *                 listeners: {
96635              *                     specialkey: function(field, e){
96636              *                         // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
96637              *                         // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
96638              *                         if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
96639              *                             var form = field.up('form').getForm();
96640              *                             form.submit();
96641              *                         }
96642              *                     }
96643              *                 }
96644              *             }
96645              *         ],
96646              *         ...
96647              *     });
96648              *
96649              * @param {Ext.form.field.Base} this
96650              * @param {Ext.EventObject} e The event object
96651              */
96652             'specialkey'
96653         );
96654
96655         // Init mixins
96656         me.initLabelable();
96657         me.initField();
96658
96659         // Default name to inputId
96660         if (!me.name) {
96661             me.name = me.getInputId();
96662         }
96663     },
96664
96665     /**
96666      * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
96667      * automatically generated.
96668      */
96669     getInputId: function() {
96670         return this.inputId || (this.inputId = Ext.id());
96671     },
96672
96673     /**
96674      * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
96675      * @return {Object} The template data
96676      * @template
96677      */
96678     getSubTplData: function() {
96679         var me = this,
96680             type = me.inputType,
96681             inputId = me.getInputId();
96682
96683         return Ext.applyIf(me.subTplData, {
96684             id: inputId,
96685             cmpId: me.id,
96686             name: me.name || inputId,
96687             type: type,
96688             size: me.size || 20,
96689             cls: me.cls,
96690             fieldCls: me.fieldCls,
96691             tabIdx: me.tabIndex,
96692             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
96693         });
96694     },
96695
96696     afterRender: function() {
96697         this.callParent();
96698         
96699         if (this.inputEl) {
96700             this.inputEl.selectable();
96701         }
96702     },
96703
96704     /**
96705      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
96706      */
96707     getSubTplMarkup: function() {
96708         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
96709     },
96710
96711     initRenderTpl: function() {
96712         var me = this;
96713         if (!me.hasOwnProperty('renderTpl')) {
96714             me.renderTpl = me.getTpl('labelableRenderTpl');
96715         }
96716         return me.callParent();
96717     },
96718
96719     initRenderData: function() {
96720         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
96721     },
96722
96723     /**
96724      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
96725      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
96726      * Ext.Element#applyStyles}.
96727      */
96728     setFieldStyle: function(style) {
96729         var me = this,
96730             inputEl = me.inputEl;
96731         if (inputEl) {
96732             inputEl.applyStyles(style);
96733         }
96734         me.fieldStyle = style;
96735     },
96736
96737     // private
96738     onRender : function() {
96739         var me = this,
96740             fieldStyle = me.fieldStyle;
96741
96742         me.onLabelableRender();
96743
96744         /**
96745          * @property {Ext.Element} inputEl
96746          * The input Element for this Field. Only available after the field has been rendered.
96747          */
96748         me.addChildEls({ name: 'inputEl', id: me.getInputId() });
96749
96750         me.callParent(arguments);
96751
96752         // Make the stored rawValue get set as the input element's value
96753         me.setRawValue(me.rawValue);
96754
96755         if (me.readOnly) {
96756             me.setReadOnly(true);
96757         }
96758         if (me.disabled) {
96759             me.disable();
96760         }
96761         if (fieldStyle) {
96762             me.setFieldStyle(fieldStyle);
96763         }
96764
96765         me.renderActiveError();
96766     },
96767
96768     initAria: function() {
96769         var me = this;
96770         me.callParent();
96771
96772         // Associate the field to the error message element
96773         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
96774     },
96775
96776     getFocusEl: function() {
96777         return this.inputEl;
96778     },
96779
96780     isFileUpload: function() {
96781         return this.inputType === 'file';
96782     },
96783
96784     extractFileInput: function() {
96785         var me = this,
96786             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
96787             clone;
96788         if (fileInput) {
96789             clone = fileInput.cloneNode(true);
96790             fileInput.parentNode.replaceChild(clone, fileInput);
96791             me.inputEl = Ext.get(clone);
96792         }
96793         return fileInput;
96794     },
96795
96796     // private override to use getSubmitValue() as a convenience
96797     getSubmitData: function() {
96798         var me = this,
96799             data = null,
96800             val;
96801         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
96802             val = me.getSubmitValue();
96803             if (val !== null) {
96804                 data = {};
96805                 data[me.getName()] = val;
96806             }
96807         }
96808         return data;
96809     },
96810
96811     /**
96812      * Returns the value that would be included in a standard form submit for this field. This will be combined with the
96813      * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
96814      * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
96815      *
96816      * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
96817      * successfully {@link #validate validated}.
96818      *
96819      * @return {String} The value to be submitted, or null.
96820      */
96821     getSubmitValue: function() {
96822         return this.processRawValue(this.getRawValue());
96823     },
96824
96825     /**
96826      * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
96827      * normalized and converted value see {@link #getValue}.
96828      * @return {String} value The raw String value of the field
96829      */
96830     getRawValue: function() {
96831         var me = this,
96832             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
96833         me.rawValue = v;
96834         return v;
96835     },
96836
96837     /**
96838      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
96839      * validation. To set the value with these additional inspections see {@link #setValue}.
96840      * @param {Object} value The value to set
96841      * @return {Object} value The field value that is set
96842      */
96843     setRawValue: function(value) {
96844         var me = this;
96845         value = Ext.value(value, '');
96846         me.rawValue = value;
96847
96848         // Some Field subclasses may not render an inputEl
96849         if (me.inputEl) {
96850             me.inputEl.dom.value = value;
96851         }
96852         return value;
96853     },
96854
96855     /**
96856      * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
96857      * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
96858      * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
96859      * to a String for display in the field.
96860      *
96861      * See {@link #rawToValue} for the opposite conversion.
96862      *
96863      * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
96864      * to an empty string.
96865      *
96866      * @param {Object} value The mixed-type value to convert to the raw representation.
96867      * @return {Object} The converted raw value.
96868      */
96869     valueToRaw: function(value) {
96870         return '' + Ext.value(value, '');
96871     },
96872
96873     /**
96874      * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
96875      * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
96876      * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
96877      *
96878      * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
96879      * to the desired object type.
96880      *
96881      * See {@link #valueToRaw} for the opposite conversion.
96882      *
96883      * The base implementation does no conversion, returning the raw value untouched.
96884      *
96885      * @param {Object} rawValue
96886      * @return {Object} The converted value.
96887      */
96888     rawToValue: function(rawValue) {
96889         return rawValue;
96890     },
96891
96892     /**
96893      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
96894      * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
96895      * nothing; individual subclasses may override this as needed.
96896      *
96897      * @param {Object} value The unprocessed string value
96898      * @return {Object} The processed string value
96899      */
96900     processRawValue: function(value) {
96901         return value;
96902     },
96903
96904     /**
96905      * Returns the current data value of the field. The type of value returned is particular to the type of the
96906      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
96907      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
96908      * @return {Object} value The field value
96909      */
96910     getValue: function() {
96911         var me = this,
96912             val = me.rawToValue(me.processRawValue(me.getRawValue()));
96913         me.value = val;
96914         return val;
96915     },
96916
96917     /**
96918      * Sets a data value into the field and runs the change detection and validation. To set the value directly
96919      * without these inspections see {@link #setRawValue}.
96920      * @param {Object} value The value to set
96921      * @return {Ext.form.field.Field} this
96922      */
96923     setValue: function(value) {
96924         var me = this;
96925         me.setRawValue(me.valueToRaw(value));
96926         return me.mixins.field.setValue.call(me, value);
96927     },
96928
96929
96930     //private
96931     onDisable: function() {
96932         var me = this,
96933             inputEl = me.inputEl;
96934         me.callParent();
96935         if (inputEl) {
96936             inputEl.dom.disabled = true;
96937         }
96938     },
96939
96940     //private
96941     onEnable: function() {
96942         var me = this,
96943             inputEl = me.inputEl;
96944         me.callParent();
96945         if (inputEl) {
96946             inputEl.dom.disabled = false;
96947         }
96948     },
96949
96950     /**
96951      * Sets the read only state of this field.
96952      * @param {Boolean} readOnly Whether the field should be read only.
96953      */
96954     setReadOnly: function(readOnly) {
96955         var me = this,
96956             inputEl = me.inputEl;
96957         if (inputEl) {
96958             inputEl.dom.readOnly = readOnly;
96959             inputEl.dom.setAttribute('aria-readonly', readOnly);
96960         }
96961         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
96962         me.readOnly = readOnly;
96963     },
96964
96965     // private
96966     fireKey: function(e){
96967         if(e.isSpecialKey()){
96968             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
96969         }
96970     },
96971
96972     // private
96973     initEvents : function(){
96974         var me = this,
96975             inputEl = me.inputEl,
96976             onChangeTask,
96977             onChangeEvent;
96978         if (inputEl) {
96979             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
96980             me.mon(inputEl, 'focus', me.onFocus, me);
96981
96982             // standardise buffer across all browsers + OS-es for consistent event order.
96983             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
96984             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
96985
96986             // listen for immediate value changes
96987             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
96988             me.onChangeEvent = onChangeEvent = function() {
96989                 onChangeTask.delay(me.checkChangeBuffer);
96990             };
96991             Ext.each(me.checkChangeEvents, function(eventName) {
96992                 if (eventName === 'propertychange') {
96993                     me.usesPropertychange = true;
96994                 }
96995                 me.mon(inputEl, eventName, onChangeEvent);
96996             }, me);
96997         }
96998         me.callParent();
96999     },
97000
97001     doComponentLayout: function() {
97002         var me = this,
97003             inputEl = me.inputEl,
97004             usesPropertychange = me.usesPropertychange,
97005             ename = 'propertychange',
97006             onChangeEvent = me.onChangeEvent;
97007
97008         // In IE if propertychange is one of the checkChangeEvents, we need to remove
97009         // the listener prior to layout and re-add it after, to prevent it from firing
97010         // needlessly for attribute and style changes applied to the inputEl.
97011         if (usesPropertychange) {
97012             me.mun(inputEl, ename, onChangeEvent);
97013         }
97014         me.callParent(arguments);
97015         if (usesPropertychange) {
97016             me.mon(inputEl, ename, onChangeEvent);
97017         }
97018     },
97019
97020     // private
97021     preFocus: Ext.emptyFn,
97022
97023     // private
97024     onFocus: function() {
97025         var me = this,
97026             focusCls = me.focusCls,
97027             inputEl = me.inputEl;
97028         me.preFocus();
97029         if (focusCls && inputEl) {
97030             inputEl.addCls(focusCls);
97031         }
97032         if (!me.hasFocus) {
97033             me.hasFocus = true;
97034             me.componentLayout.onFocus();
97035             me.fireEvent('focus', me);
97036         }
97037     },
97038
97039     // private
97040     beforeBlur : Ext.emptyFn,
97041
97042     // private
97043     onBlur : function(){
97044         var me = this,
97045             focusCls = me.focusCls,
97046             inputEl = me.inputEl;
97047
97048         if (me.destroying) {
97049             return;
97050         }
97051
97052         me.beforeBlur();
97053         if (focusCls && inputEl) {
97054             inputEl.removeCls(focusCls);
97055         }
97056         if (me.validateOnBlur) {
97057             me.validate();
97058         }
97059         me.hasFocus = false;
97060         me.fireEvent('blur', me);
97061         me.postBlur();
97062     },
97063
97064     // private
97065     postBlur : Ext.emptyFn,
97066
97067
97068     /**
97069      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
97070      * @param {Boolean} isDirty
97071      */
97072     onDirtyChange: function(isDirty) {
97073         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
97074     },
97075
97076
97077     /**
97078      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
97079      * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
97080      * always treated as valid.
97081      *
97082      * @return {Boolean} True if the value is valid, else false
97083      */
97084     isValid : function() {
97085         var me = this;
97086         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
97087     },
97088
97089
97090     /**
97091      * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
97092      * {@link #markInvalid} and false is returned, otherwise true is returned.
97093      *
97094      * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
97095      * onwards {@link #getErrors} should be overridden instead.
97096      *
97097      * @param {Object} value The value to validate
97098      * @return {Boolean} True if all validations passed, false if one or more failed
97099      */
97100     validateValue: function(value) {
97101         var me = this,
97102             errors = me.getErrors(value),
97103             isValid = Ext.isEmpty(errors);
97104         if (!me.preventMark) {
97105             if (isValid) {
97106                 me.clearInvalid();
97107             } else {
97108                 me.markInvalid(errors);
97109             }
97110         }
97111
97112         return isValid;
97113     },
97114
97115     /**
97116      * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
97117      * display the messages and applying {@link #invalidCls} to the field's UI element.
97118      *
97119      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
97120      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
97121      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
97122      *
97123      * @param {String/String[]} errors The validation message(s) to display.
97124      */
97125     markInvalid : function(errors) {
97126         // Save the message and fire the 'invalid' event
97127         var me = this,
97128             oldMsg = me.getActiveError();
97129         me.setActiveErrors(Ext.Array.from(errors));
97130         if (oldMsg !== me.getActiveError()) {
97131             me.doComponentLayout();
97132         }
97133     },
97134
97135     /**
97136      * Clear any invalid styles/messages for this field.
97137      *
97138      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
97139      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
97140      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
97141      */
97142     clearInvalid : function() {
97143         // Clear the message and fire the 'valid' event
97144         var me = this,
97145             hadError = me.hasActiveError();
97146         me.unsetActiveError();
97147         if (hadError) {
97148             me.doComponentLayout();
97149         }
97150     },
97151
97152     /**
97153      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
97154      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
97155      */
97156     renderActiveError: function() {
97157         var me = this,
97158             hasError = me.hasActiveError();
97159         if (me.inputEl) {
97160             // Add/remove invalid class
97161             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
97162         }
97163         me.mixins.labelable.renderActiveError.call(me);
97164     },
97165
97166
97167     getActionEl: function() {
97168         return this.inputEl || this.el;
97169     }
97170
97171 });
97172
97173 /**
97174  * @docauthor Jason Johnston <jason@sencha.com>
97175  *
97176  * A basic text field.  Can be used as a direct replacement for traditional text inputs,
97177  * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
97178  * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
97179  *
97180  * # Validation
97181  *
97182  * The Text field has a useful set of validations built in:
97183  *
97184  * - {@link #allowBlank} for making the field required
97185  * - {@link #minLength} for requiring a minimum value length
97186  * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
97187  *   as the `maxlength` attribute on the input element)
97188  * - {@link #regex} to specify a custom regular expression for validation
97189  *
97190  * In addition, custom validations may be added:
97191  *
97192  * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
97193  *   custom validation logic
97194  * - {@link #validator} allows a custom arbitrary function to be called during validation
97195  *
97196  * The details around how and when each of these validation options get used are described in the
97197  * documentation for {@link #getErrors}.
97198  *
97199  * By default, the field value is checked for validity immediately while the user is typing in the
97200  * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
97201  * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
97202  * {@link Ext.form.Panel} class documentation.
97203  *
97204  * # Masking and Character Stripping
97205  *
97206  * Text fields can be configured with custom regular expressions to be applied to entered values before
97207  * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
97208  *
97209  * # Example usage
97210  *
97211  *     @example
97212  *     Ext.create('Ext.form.Panel', {
97213  *         title: 'Contact Info',
97214  *         width: 300,
97215  *         bodyPadding: 10,
97216  *         renderTo: Ext.getBody(),
97217  *         items: [{
97218  *             xtype: 'textfield',
97219  *             name: 'name',
97220  *             fieldLabel: 'Name',
97221  *             allowBlank: false  // requires a non-empty value
97222  *         }, {
97223  *             xtype: 'textfield',
97224  *             name: 'email',
97225  *             fieldLabel: 'Email Address',
97226  *             vtype: 'email'  // requires value to be a valid email address format
97227  *         }]
97228  *     });
97229  */
97230 Ext.define('Ext.form.field.Text', {
97231     extend:'Ext.form.field.Base',
97232     alias: 'widget.textfield',
97233     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
97234     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
97235
97236     /**
97237      * @cfg {String} vtypeText
97238      * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
97239      * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
97240      */
97241
97242     /**
97243      * @cfg {RegExp} stripCharsRe
97244      * A JavaScript RegExp object used to strip unwanted content from the value
97245      * before validation. If <tt>stripCharsRe</tt> is specified,
97246      * every character matching <tt>stripCharsRe</tt> will be removed before fed to validation.
97247      * This does not change the value of the field.
97248      */
97249
97250     /**
97251      * @cfg {Number} size
97252      * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
97253      * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
97254      */
97255     size: 20,
97256
97257     /**
97258      * @cfg {Boolean} [grow=false]
97259      * true if this field should automatically grow and shrink to its content
97260      */
97261
97262     /**
97263      * @cfg {Number} growMin
97264      * The minimum width to allow when `{@link #grow} = true`
97265      */
97266     growMin : 30,
97267
97268     /**
97269      * @cfg {Number} growMax
97270      * The maximum width to allow when `{@link #grow} = true`
97271      */
97272     growMax : 800,
97273
97274     /**
97275      * @cfg {String} growAppend
97276      * A string that will be appended to the field's current value for the purposes of calculating the target field
97277      * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
97278      * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
97279      * width is adjusted.
97280      */
97281     growAppend: 'W',
97282
97283     /**
97284      * @cfg {String} vtype
97285      * A validation type name as defined in {@link Ext.form.field.VTypes}
97286      */
97287
97288     /**
97289      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
97290      * typed) that do not match.
97291      * Note: It dose not filter characters already in the input.
97292      */
97293
97294     /**
97295      * @cfg {Boolean} [disableKeyFilter=false]
97296      * Specify true to disable input keystroke filtering
97297      */
97298
97299     /**
97300      * @cfg {Boolean} allowBlank
97301      * Specify false to validate that the value's length is > 0
97302      */
97303     allowBlank : true,
97304
97305     /**
97306      * @cfg {Number} minLength
97307      * Minimum input field length required
97308      */
97309     minLength : 0,
97310
97311     /**
97312      * @cfg {Number} maxLength
97313      * Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). This behavior is intended to
97314      * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
97315      * tracking. To restrict the maximum number of characters that can be entered into the field use the **{@link
97316      * Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
97317      */
97318     maxLength : Number.MAX_VALUE,
97319
97320     /**
97321      * @cfg {Boolean} enforceMaxLength
97322      * True to set the maxLength property on the underlying input field. Defaults to false
97323      */
97324
97325     /**
97326      * @cfg {String} minLengthText
97327      * Error text to display if the **{@link #minLength minimum length}** validation fails.
97328      */
97329     minLengthText : 'The minimum length for this field is {0}',
97330
97331     /**
97332      * @cfg {String} maxLengthText
97333      * Error text to display if the **{@link #maxLength maximum length}** validation fails
97334      */
97335     maxLengthText : 'The maximum length for this field is {0}',
97336
97337     /**
97338      * @cfg {Boolean} [selectOnFocus=false]
97339      * true to automatically select any existing field text when the field receives input focus
97340      */
97341
97342     /**
97343      * @cfg {String} blankText
97344      * The error text to display if the **{@link #allowBlank}** validation fails
97345      */
97346     blankText : 'This field is required',
97347
97348     /**
97349      * @cfg {Function} validator
97350      * A custom validation function to be called during field validation ({@link #getErrors}).
97351      * If specified, this function will be called first, allowing the developer to override the default validation
97352      * process.
97353      *
97354      * This function will be passed the following parameters:
97355      *
97356      * @cfg {Object} validator.value The current field value
97357      * @cfg {Boolean/String} validator.return
97358      *
97359      * - True if the value is valid
97360      * - An error message if the value is invalid
97361      */
97362
97363     /**
97364      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation.
97365      * If the test fails, the field will be marked invalid using
97366      * either <b><tt>{@link #regexText}</tt></b> or <b><tt>{@link #invalidText}</tt></b>.
97367      */
97368
97369     /**
97370      * @cfg {String} regexText
97371      * The error text to display if **{@link #regex}** is used and the test fails during validation
97372      */
97373     regexText : '',
97374
97375     /**
97376      * @cfg {String} emptyText
97377      * The default text to place into an empty field.
97378      *
97379      * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
97380      * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
97381      * false.
97382      *
97383      * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
97384      * avoided.
97385      */
97386
97387     /**
97388      * @cfg {String} [emptyCls='x-form-empty-field']
97389      * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
97390      * This class is automatically added and removed as needed depending on the current field value.
97391      */
97392     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
97393
97394     ariaRole: 'textbox',
97395
97396     /**
97397      * @cfg {Boolean} [enableKeyEvents=false]
97398      * true to enable the proxying of key events for the HTML input field
97399      */
97400
97401     componentLayout: 'textfield',
97402
97403     initComponent : function(){
97404         this.callParent();
97405         this.addEvents(
97406             /**
97407              * @event autosize
97408              * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
97409              * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
97410              * developer to apply additional logic at runtime to resize the field if needed.
97411              * @param {Ext.form.field.Text} this This text field
97412              * @param {Number} width The new field width
97413              */
97414             'autosize',
97415
97416             /**
97417              * @event keydown
97418              * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
97419              * @param {Ext.form.field.Text} this This text field
97420              * @param {Ext.EventObject} e
97421              */
97422             'keydown',
97423             /**
97424              * @event keyup
97425              * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
97426              * @param {Ext.form.field.Text} this This text field
97427              * @param {Ext.EventObject} e
97428              */
97429             'keyup',
97430             /**
97431              * @event keypress
97432              * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
97433              * @param {Ext.form.field.Text} this This text field
97434              * @param {Ext.EventObject} e
97435              */
97436             'keypress'
97437         );
97438     },
97439
97440     // private
97441     initEvents : function(){
97442         var me = this,
97443             el = me.inputEl;
97444
97445         me.callParent();
97446         if(me.selectOnFocus || me.emptyText){
97447             me.mon(el, 'mousedown', me.onMouseDown, me);
97448         }
97449         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
97450             me.mon(el, 'keypress', me.filterKeys, me);
97451         }
97452
97453         if (me.enableKeyEvents) {
97454             me.mon(el, {
97455                 scope: me,
97456                 keyup: me.onKeyUp,
97457                 keydown: me.onKeyDown,
97458                 keypress: me.onKeyPress
97459             });
97460         }
97461     },
97462
97463     /**
97464      * @private
97465      * Override. Treat undefined and null values as equal to an empty string value.
97466      */
97467     isEqual: function(value1, value2) {
97468         return this.isEqualAsString(value1, value2);
97469     },
97470
97471     /**
97472      * @private
97473      * If grow=true, invoke the autoSize method when the field's value is changed.
97474      */
97475     onChange: function() {
97476         this.callParent();
97477         this.autoSize();
97478     },
97479
97480     afterRender: function(){
97481         var me = this;
97482         if (me.enforceMaxLength) {
97483             me.inputEl.dom.maxLength = me.maxLength;
97484         }
97485         me.applyEmptyText();
97486         me.autoSize();
97487         me.callParent();
97488     },
97489
97490     onMouseDown: function(e){
97491         var me = this;
97492         if(!me.hasFocus){
97493             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
97494         }
97495     },
97496
97497     /**
97498      * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
97499      * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
97500      * to the raw value.
97501      * @param {String} value The unprocessed string value
97502      * @return {String} The processed string value
97503      */
97504     processRawValue: function(value) {
97505         var me = this,
97506             stripRe = me.stripCharsRe,
97507             newValue;
97508
97509         if (stripRe) {
97510             newValue = value.replace(stripRe, '');
97511             if (newValue !== value) {
97512                 me.setRawValue(newValue);
97513                 value = newValue;
97514             }
97515         }
97516         return value;
97517     },
97518
97519     //private
97520     onDisable: function(){
97521         this.callParent();
97522         if (Ext.isIE) {
97523             this.inputEl.dom.unselectable = 'on';
97524         }
97525     },
97526
97527     //private
97528     onEnable: function(){
97529         this.callParent();
97530         if (Ext.isIE) {
97531             this.inputEl.dom.unselectable = '';
97532         }
97533     },
97534
97535     onKeyDown: function(e) {
97536         this.fireEvent('keydown', this, e);
97537     },
97538
97539     onKeyUp: function(e) {
97540         this.fireEvent('keyup', this, e);
97541     },
97542
97543     onKeyPress: function(e) {
97544         this.fireEvent('keypress', this, e);
97545     },
97546
97547     /**
97548      * Resets the current field value to the originally-loaded value and clears any validation messages.
97549      * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
97550      */
97551     reset : function(){
97552         this.callParent();
97553         this.applyEmptyText();
97554     },
97555
97556     applyEmptyText : function(){
97557         var me = this,
97558             emptyText = me.emptyText,
97559             isEmpty;
97560
97561         if (me.rendered && emptyText) {
97562             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
97563
97564             if (Ext.supports.Placeholder) {
97565                 me.inputEl.dom.placeholder = emptyText;
97566             } else if (isEmpty) {
97567                 me.setRawValue(emptyText);
97568             }
97569
97570             //all browsers need this because of a styling issue with chrome + placeholders.
97571             //the text isnt vertically aligned when empty (and using the placeholder)
97572             if (isEmpty) {
97573                 me.inputEl.addCls(me.emptyCls);
97574             }
97575
97576             me.autoSize();
97577         }
97578     },
97579
97580     // private
97581     preFocus : function(){
97582         var me = this,
97583             inputEl = me.inputEl,
97584             emptyText = me.emptyText,
97585             isEmpty;
97586
97587         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
97588             me.setRawValue('');
97589             isEmpty = true;
97590             inputEl.removeCls(me.emptyCls);
97591         } else if (Ext.supports.Placeholder) {
97592             me.inputEl.removeCls(me.emptyCls);
97593         }
97594         if (me.selectOnFocus || isEmpty) {
97595             inputEl.dom.select();
97596         }
97597     },
97598
97599     onFocus: function() {
97600         var me = this;
97601         me.callParent(arguments);
97602         if (me.emptyText) {
97603             me.autoSize();
97604         }
97605     },
97606
97607     // private
97608     postBlur : function(){
97609         this.applyEmptyText();
97610     },
97611
97612     // private
97613     filterKeys : function(e){
97614         /*
97615          * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
97616          * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
97617          * so we can still process these special characters.
97618          */
97619         if (e.ctrlKey && !e.altKey) {
97620             return;
97621         }
97622         var key = e.getKey(),
97623             charCode = String.fromCharCode(e.getCharCode());
97624
97625         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
97626             return;
97627         }
97628
97629         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
97630             return;
97631         }
97632         if(!this.maskRe.test(charCode)){
97633             e.stopEvent();
97634         }
97635     },
97636
97637     /**
97638      * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
97639      * the current value of the input element if the field has been rendered, ignoring the value if it is the
97640      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
97641      * @return {String} The raw String value of the field
97642      */
97643     getRawValue: function() {
97644         var me = this,
97645             v = me.callParent();
97646         if (v === me.emptyText) {
97647             v = '';
97648         }
97649         return v;
97650     },
97651
97652     /**
97653      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
97654      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
97655      * @param {Object} value The value to set
97656      * @return {Ext.form.field.Text} this
97657      */
97658     setValue: function(value) {
97659         var me = this,
97660             inputEl = me.inputEl;
97661
97662         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
97663             inputEl.removeCls(me.emptyCls);
97664         }
97665
97666         me.callParent(arguments);
97667
97668         me.applyEmptyText();
97669         return me;
97670     },
97671
97672     /**
97673      * Validates a value according to the field's validation rules and returns an array of errors
97674      * for any failing validations. Validation rules are processed in the following order:
97675      *
97676      * 1. **Field specific validator**
97677      *
97678      *     A validator offers a way to customize and reuse a validation specification.
97679      *     If a field is configured with a `{@link #validator}`
97680      *     function, it will be passed the current field value.  The `{@link #validator}`
97681      *     function is expected to return either:
97682      *
97683      *     - Boolean `true`  if the value is valid (validation continues).
97684      *     - a String to represent the invalid message if invalid (validation halts).
97685      *
97686      * 2. **Basic Validation**
97687      *
97688      *     If the `{@link #validator}` has not halted validation,
97689      *     basic validation proceeds as follows:
97690      *
97691      *     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
97692      *
97693      *         Depending on the configuration of `{@link #allowBlank}`, a
97694      *         blank field will cause validation to halt at this step and return
97695      *         Boolean true or false accordingly.
97696      *
97697      *     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
97698      *
97699      *         If the passed value does not satisfy the `{@link #minLength}`
97700      *         specified, validation halts.
97701      *
97702      *     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
97703      *
97704      *         If the passed value does not satisfy the `{@link #maxLength}`
97705      *         specified, validation halts.
97706      *
97707      * 3. **Preconfigured Validation Types (VTypes)**
97708      *
97709      *     If none of the prior validation steps halts validation, a field
97710      *     configured with a `{@link #vtype}` will utilize the
97711      *     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
97712      *     If invalid, either the field's `{@link #vtypeText}` or
97713      *     the VTypes vtype Text property will be used for the invalid message.
97714      *     Keystrokes on the field will be filtered according to the VTypes
97715      *     vtype Mask property.
97716      *
97717      * 4. **Field specific regex test**
97718      *
97719      *     If none of the prior validation steps halts validation, a field's
97720      *     configured <code>{@link #regex}</code> test will be processed.
97721      *     The invalid message for this test is configured with `{@link #regexText}`
97722      *
97723      * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
97724      * @return {String[]} Array of any validation errors
97725      */
97726     getErrors: function(value) {
97727         var me = this,
97728             errors = me.callParent(arguments),
97729             validator = me.validator,
97730             emptyText = me.emptyText,
97731             allowBlank = me.allowBlank,
97732             vtype = me.vtype,
97733             vtypes = Ext.form.field.VTypes,
97734             regex = me.regex,
97735             format = Ext.String.format,
97736             msg;
97737
97738         value = value || me.processRawValue(me.getRawValue());
97739
97740         if (Ext.isFunction(validator)) {
97741             msg = validator.call(me, value);
97742             if (msg !== true) {
97743                 errors.push(msg);
97744             }
97745         }
97746
97747         if (value.length < 1 || value === emptyText) {
97748             if (!allowBlank) {
97749                 errors.push(me.blankText);
97750             }
97751             //if value is blank, there cannot be any additional errors
97752             return errors;
97753         }
97754
97755         if (value.length < me.minLength) {
97756             errors.push(format(me.minLengthText, me.minLength));
97757         }
97758
97759         if (value.length > me.maxLength) {
97760             errors.push(format(me.maxLengthText, me.maxLength));
97761         }
97762
97763         if (vtype) {
97764             if(!vtypes[vtype](value, me)){
97765                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
97766             }
97767         }
97768
97769         if (regex && !regex.test(value)) {
97770             errors.push(me.regexText || me.invalidText);
97771         }
97772
97773         return errors;
97774     },
97775
97776     /**
97777      * Selects text in this field
97778      * @param {Number} [start=0] The index where the selection should start
97779      * @param {Number} [end] The index where the selection should end (defaults to the text length)
97780      */
97781     selectText : function(start, end){
97782         var me = this,
97783             v = me.getRawValue(),
97784             doFocus = true,
97785             el = me.inputEl.dom,
97786             undef,
97787             range;
97788
97789         if (v.length > 0) {
97790             start = start === undef ? 0 : start;
97791             end = end === undef ? v.length : end;
97792             if (el.setSelectionRange) {
97793                 el.setSelectionRange(start, end);
97794             }
97795             else if(el.createTextRange) {
97796                 range = el.createTextRange();
97797                 range.moveStart('character', start);
97798                 range.moveEnd('character', end - v.length);
97799                 range.select();
97800             }
97801             doFocus = Ext.isGecko || Ext.isOpera;
97802         }
97803         if (doFocus) {
97804             me.focus();
97805         }
97806     },
97807
97808     /**
97809      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
97810      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
97811      */
97812     autoSize: function() {
97813         var me = this,
97814             width;
97815         if (me.grow && me.rendered) {
97816             me.doComponentLayout();
97817             width = me.inputEl.getWidth();
97818             if (width !== me.lastInputWidth) {
97819                 me.fireEvent('autosize', width);
97820                 me.lastInputWidth = width;
97821             }
97822         }
97823     },
97824
97825     initAria: function() {
97826         this.callParent();
97827         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
97828     },
97829
97830     /**
97831      * To get the natural width of the inputEl, we do a simple calculation based on the 'size' config. We use
97832      * hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which would hurt
97833      * performance. Overrides Labelable method.
97834      * @protected
97835      */
97836     getBodyNaturalWidth: function() {
97837         return Math.round(this.size * 6.5) + 20;
97838     }
97839
97840 });
97841
97842 /**
97843  * @docauthor Robert Dougan <rob@sencha.com>
97844  *
97845  * This class creates a multiline text field, which can be used as a direct replacement for traditional
97846  * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
97847  * fit its content.
97848  *
97849  * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
97850  *
97851  * Example usage:
97852  *
97853  *     @example
97854  *     Ext.create('Ext.form.FormPanel', {
97855  *         title      : 'Sample TextArea',
97856  *         width      : 400,
97857  *         bodyPadding: 10,
97858  *         renderTo   : Ext.getBody(),
97859  *         items: [{
97860  *             xtype     : 'textareafield',
97861  *             grow      : true,
97862  *             name      : 'message',
97863  *             fieldLabel: 'Message',
97864  *             anchor    : '100%'
97865  *         }]
97866  *     });
97867  *
97868  * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
97869  * These allow you to set the minimum and maximum grow heights for the textarea.
97870  */
97871 Ext.define('Ext.form.field.TextArea', {
97872     extend:'Ext.form.field.Text',
97873     alias: ['widget.textareafield', 'widget.textarea'],
97874     alternateClassName: 'Ext.form.TextArea',
97875     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
97876
97877     fieldSubTpl: [
97878         '<textarea id="{id}" ',
97879             '<tpl if="name">name="{name}" </tpl>',
97880             '<tpl if="rows">rows="{rows}" </tpl>',
97881             '<tpl if="cols">cols="{cols}" </tpl>',
97882             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
97883             'class="{fieldCls} {typeCls}" ',
97884             'autocomplete="off">',
97885         '</textarea>',
97886         {
97887             compiled: true,
97888             disableFormats: true
97889         }
97890     ],
97891
97892     /**
97893      * @cfg {Number} growMin
97894      * The minimum height to allow when {@link #grow}=true
97895      */
97896     growMin: 60,
97897
97898     /**
97899      * @cfg {Number} growMax
97900      * The maximum height to allow when {@link #grow}=true
97901      */
97902     growMax: 1000,
97903
97904     /**
97905      * @cfg {String} growAppend
97906      * A string that will be appended to the field's current value for the purposes of calculating the target field
97907      * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
97908      * always a space below the current line.
97909      */
97910     growAppend: '\n-',
97911
97912     /**
97913      * @cfg {Number} cols
97914      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97915      * configured {@link #width} and is not given a width by its container's layout.
97916      */
97917     cols: 20,
97918
97919     /**
97920      * @cfg {Number} cols
97921      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97922      * configured {@link #width} and is not given a width by its container's layout.
97923      */
97924     rows: 4,
97925
97926     /**
97927      * @cfg {Boolean} enterIsSpecial
97928      * True if you want the enter key to be classed as a special key. Special keys are generally navigation keys
97929      * (arrows, space, enter). Setting the config property to true would mean that you could not insert returns into the
97930      * textarea.
97931      */
97932     enterIsSpecial: false,
97933
97934     /**
97935      * @cfg {Boolean} preventScrollbars
97936      * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
97937      * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
97938      */
97939     preventScrollbars: false,
97940
97941     // private
97942     componentLayout: 'textareafield',
97943
97944     // private
97945     onRender: function(ct, position) {
97946         var me = this;
97947         Ext.applyIf(me.subTplData, {
97948             cols: me.cols,
97949             rows: me.rows
97950         });
97951
97952         me.callParent(arguments);
97953     },
97954
97955     // private
97956     afterRender: function(){
97957         var me = this;
97958
97959         me.callParent(arguments);
97960
97961         if (me.grow) {
97962             if (me.preventScrollbars) {
97963                 me.inputEl.setStyle('overflow', 'hidden');
97964             }
97965             me.inputEl.setHeight(me.growMin);
97966         }
97967     },
97968
97969     // private
97970     fireKey: function(e) {
97971         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
97972             this.fireEvent('specialkey', this, e);
97973         }
97974     },
97975
97976     /**
97977      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
97978      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
97979      */
97980     autoSize: function() {
97981         var me = this,
97982             height;
97983
97984         if (me.grow && me.rendered) {
97985             me.doComponentLayout();
97986             height = me.inputEl.getHeight();
97987             if (height !== me.lastInputHeight) {
97988                 me.fireEvent('autosize', height);
97989                 me.lastInputHeight = height;
97990             }
97991         }
97992     },
97993
97994     // private
97995     initAria: function() {
97996         this.callParent(arguments);
97997         this.getActionEl().dom.setAttribute('aria-multiline', true);
97998     },
97999
98000     /**
98001      * To get the natural width of the textarea element, we do a simple calculation based on the 'cols' config.
98002      * We use hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which
98003      * would hurt performance. Overrides Labelable method.
98004      * @protected
98005      */
98006     getBodyNaturalWidth: function() {
98007         return Math.round(this.cols * 6.5) + 20;
98008     }
98009
98010 });
98011
98012
98013 /**
98014  * Utility class for generating different styles of message boxes.  The singleton instance, Ext.MessageBox
98015  * alias `Ext.Msg` can also be used.
98016  *
98017  * Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
98018  * browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
98019  * that should only run *after* some user feedback from the MessageBox, you must use a callback function
98020  * (see the `function` parameter for {@link #show} for more details).
98021  *
98022  * Basic alert
98023  *
98024  *     @example
98025  *     Ext.Msg.alert('Status', 'Changes saved successfully.');
98026  *
98027  * Prompt for user data and process the result using a callback
98028  *
98029  *     @example
98030  *     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
98031  *         if (btn == 'ok'){
98032  *             // process text value and close...
98033  *         }
98034  *     });
98035  *
98036  * Show a dialog using config options
98037  *
98038  *     @example
98039  *     Ext.Msg.show({
98040  *          title:'Save Changes?',
98041  *          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
98042  *          buttons: Ext.Msg.YESNOCANCEL,
98043  *          icon: Ext.Msg.QUESTION
98044  *     });
98045  */
98046 Ext.define('Ext.window.MessageBox', {
98047     extend: 'Ext.window.Window',
98048
98049     requires: [
98050         'Ext.toolbar.Toolbar',
98051         'Ext.form.field.Text',
98052         'Ext.form.field.TextArea',
98053         'Ext.button.Button',
98054         'Ext.layout.container.Anchor',
98055         'Ext.layout.container.HBox',
98056         'Ext.ProgressBar'
98057     ],
98058
98059     alias: 'widget.messagebox',
98060
98061     /**
98062      * Button config that displays a single OK button
98063      * @type Number
98064      */
98065     OK : 1,
98066     /**
98067      * Button config that displays a single Yes button
98068      * @type Number
98069      */
98070     YES : 2,
98071     /**
98072      * Button config that displays a single No button
98073      * @type Number
98074      */
98075     NO : 4,
98076     /**
98077      * Button config that displays a single Cancel button
98078      * @type Number
98079      */
98080     CANCEL : 8,
98081     /**
98082      * Button config that displays OK and Cancel buttons
98083      * @type Number
98084      */
98085     OKCANCEL : 9,
98086     /**
98087      * Button config that displays Yes and No buttons
98088      * @type Number
98089      */
98090     YESNO : 6,
98091     /**
98092      * Button config that displays Yes, No and Cancel buttons
98093      * @type Number
98094      */
98095     YESNOCANCEL : 14,
98096     /**
98097      * The CSS class that provides the INFO icon image
98098      * @type String
98099      */
98100     INFO : 'ext-mb-info',
98101     /**
98102      * The CSS class that provides the WARNING icon image
98103      * @type String
98104      */
98105     WARNING : 'ext-mb-warning',
98106     /**
98107      * The CSS class that provides the QUESTION icon image
98108      * @type String
98109      */
98110     QUESTION : 'ext-mb-question',
98111     /**
98112      * The CSS class that provides the ERROR icon image
98113      * @type String
98114      */
98115     ERROR : 'ext-mb-error',
98116
98117     // hide it by offsets. Windows are hidden on render by default.
98118     hideMode: 'offsets',
98119     closeAction: 'hide',
98120     resizable: false,
98121     title: '&#160;',
98122
98123     width: 600,
98124     height: 500,
98125     minWidth: 250,
98126     maxWidth: 600,
98127     minHeight: 110,
98128     maxHeight: 500,
98129     constrain: true,
98130
98131     cls: Ext.baseCSSPrefix + 'message-box',
98132
98133     layout: {
98134         type: 'anchor'
98135     },
98136
98137     /**
98138      * The default height in pixels of the message box's multiline textarea if displayed.
98139      * @type Number
98140      */
98141     defaultTextHeight : 75,
98142     /**
98143      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
98144      * for setting a different minimum width than text-only dialogs may need.
98145      * @type Number
98146      */
98147     minProgressWidth : 250,
98148     /**
98149      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
98150      * for setting a different minimum width than text-only dialogs may need.
98151      * @type Number
98152      */
98153     minPromptWidth: 250,
98154     /**
98155      * An object containing the default button text strings that can be overriden for localized language support.
98156      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
98157      * resource file for handling language support across the framework.
98158      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
98159      * @type Object
98160      */
98161     buttonText: {
98162         ok: 'OK',
98163         yes: 'Yes',
98164         no: 'No',
98165         cancel: 'Cancel'
98166     },
98167
98168     buttonIds: [
98169         'ok', 'yes', 'no', 'cancel'
98170     ],
98171
98172     titleText: {
98173         confirm: 'Confirm',
98174         prompt: 'Prompt',
98175         wait: 'Loading...',
98176         alert: 'Attention'
98177     },
98178
98179     iconHeight: 35,
98180
98181     makeButton: function(btnIdx) {
98182         var btnId = this.buttonIds[btnIdx];
98183         return Ext.create('Ext.button.Button', {
98184             handler: this.btnCallback,
98185             itemId: btnId,
98186             scope: this,
98187             text: this.buttonText[btnId],
98188             minWidth: 75
98189         });
98190     },
98191
98192     btnCallback: function(btn) {
98193         var me = this,
98194             value,
98195             field;
98196
98197         if (me.cfg.prompt || me.cfg.multiline) {
98198             if (me.cfg.multiline) {
98199                 field = me.textArea;
98200             } else {
98201                 field = me.textField;
98202             }
98203             value = field.getValue();
98204             field.reset();
98205         }
98206
98207         // Important not to have focus remain in the hidden Window; Interferes with DnD.
98208         btn.blur();
98209         me.hide();
98210         me.userCallback(btn.itemId, value, me.cfg);
98211     },
98212
98213     hide: function() {
98214         var me = this;
98215         me.dd.endDrag();
98216         me.progressBar.reset();
98217         me.removeCls(me.cfg.cls);
98218         me.callParent();
98219     },
98220
98221     initComponent: function() {
98222         var me = this,
98223             i, button;
98224
98225         me.title = '&#160;';
98226
98227         me.topContainer = Ext.create('Ext.container.Container', {
98228             anchor: '100%',
98229             style: {
98230                 padding: '10px',
98231                 overflow: 'hidden'
98232             },
98233             items: [
98234                 me.iconComponent = Ext.create('Ext.Component', {
98235                     cls: 'ext-mb-icon',
98236                     width: 50,
98237                     height: me.iconHeight,
98238                     style: {
98239                         'float': 'left'
98240                     }
98241                 }),
98242                 me.promptContainer = Ext.create('Ext.container.Container', {
98243                     layout: {
98244                         type: 'anchor'
98245                     },
98246                     items: [
98247                         me.msg = Ext.create('Ext.Component', {
98248                             autoEl: { tag: 'span' },
98249                             cls: 'ext-mb-text'
98250                         }),
98251                         me.textField = Ext.create('Ext.form.field.Text', {
98252                             anchor: '100%',
98253                             enableKeyEvents: true,
98254                             listeners: {
98255                                 keydown: me.onPromptKey,
98256                                 scope: me
98257                             }
98258                         }),
98259                         me.textArea = Ext.create('Ext.form.field.TextArea', {
98260                             anchor: '100%',
98261                             height: 75
98262                         })
98263                     ]
98264                 })
98265             ]
98266         });
98267         me.progressBar = Ext.create('Ext.ProgressBar', {
98268             anchor: '-10',
98269             style: 'margin-left:10px'
98270         });
98271
98272         me.items = [me.topContainer, me.progressBar];
98273
98274         // Create the buttons based upon passed bitwise config
98275         me.msgButtons = [];
98276         for (i = 0; i < 4; i++) {
98277             button = me.makeButton(i);
98278             me.msgButtons[button.itemId] = button;
98279             me.msgButtons.push(button);
98280         }
98281         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
98282             ui: 'footer',
98283             dock: 'bottom',
98284             layout: {
98285                 pack: 'center'
98286             },
98287             items: [
98288                 me.msgButtons[0],
98289                 me.msgButtons[1],
98290                 me.msgButtons[2],
98291                 me.msgButtons[3]
98292             ]
98293         });
98294         me.dockedItems = [me.bottomTb];
98295
98296         me.callParent();
98297     },
98298
98299     onPromptKey: function(textField, e) {
98300         var me = this,
98301             blur;
98302
98303         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
98304             if (me.msgButtons.ok.isVisible()) {
98305                 blur = true;
98306                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
98307             } else if (me.msgButtons.yes.isVisible()) {
98308                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
98309                 blur = true;
98310             }
98311
98312             if (blur) {
98313                 me.textField.blur();
98314             }
98315         }
98316     },
98317
98318     reconfigure: function(cfg) {
98319         var me = this,
98320             buttons = cfg.buttons || 0,
98321             hideToolbar = true,
98322             initialWidth = me.maxWidth,
98323             i;
98324
98325         cfg = cfg || {};
98326         me.cfg = cfg;
98327         if (cfg.width) {
98328             initialWidth = cfg.width;
98329         }
98330
98331         // Default to allowing the Window to take focus.
98332         delete me.defaultFocus;
98333
98334         // clear any old animateTarget
98335         me.animateTarget = cfg.animateTarget || undefined;
98336
98337         // Defaults to modal
98338         me.modal = cfg.modal !== false;
98339
98340         // Show the title
98341         if (cfg.title) {
98342             me.setTitle(cfg.title||'&#160;');
98343         }
98344
98345         if (!me.rendered) {
98346             me.width = initialWidth;
98347             me.render(Ext.getBody());
98348         } else {
98349             me.setSize(initialWidth, me.maxHeight);
98350         }
98351         me.setPosition(-10000, -10000);
98352
98353         // Hide or show the close tool
98354         me.closable = cfg.closable && !cfg.wait;
98355         me.header.child('[type=close]').setVisible(cfg.closable !== false);
98356
98357         // Hide or show the header
98358         if (!cfg.title && !me.closable) {
98359             me.header.hide();
98360         } else {
98361             me.header.show();
98362         }
98363
98364         // Default to dynamic drag: drag the window, not a ghost
98365         me.liveDrag = !cfg.proxyDrag;
98366
98367         // wrap the user callback
98368         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
98369
98370         // Hide or show the icon Component
98371         me.setIcon(cfg.icon);
98372
98373         // Hide or show the message area
98374         if (cfg.msg) {
98375             me.msg.update(cfg.msg);
98376             me.msg.show();
98377         } else {
98378             me.msg.hide();
98379         }
98380
98381         // Hide or show the input field
98382         if (cfg.prompt || cfg.multiline) {
98383             me.multiline = cfg.multiline;
98384             if (cfg.multiline) {
98385                 me.textArea.setValue(cfg.value);
98386                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
98387                 me.textArea.show();
98388                 me.textField.hide();
98389                 me.defaultFocus = me.textArea;
98390             } else {
98391                 me.textField.setValue(cfg.value);
98392                 me.textArea.hide();
98393                 me.textField.show();
98394                 me.defaultFocus = me.textField;
98395             }
98396         } else {
98397             me.textArea.hide();
98398             me.textField.hide();
98399         }
98400
98401         // Hide or show the progress bar
98402         if (cfg.progress || cfg.wait) {
98403             me.progressBar.show();
98404             me.updateProgress(0, cfg.progressText);
98405             if(cfg.wait === true){
98406                 me.progressBar.wait(cfg.waitConfig);
98407             }
98408         } else {
98409             me.progressBar.hide();
98410         }
98411
98412         // Hide or show buttons depending on flag value sent.
98413         for (i = 0; i < 4; i++) {
98414             if (buttons & Math.pow(2, i)) {
98415
98416                 // Default to focus on the first visible button if focus not already set
98417                 if (!me.defaultFocus) {
98418                     me.defaultFocus = me.msgButtons[i];
98419                 }
98420                 me.msgButtons[i].show();
98421                 hideToolbar = false;
98422             } else {
98423                 me.msgButtons[i].hide();
98424             }
98425         }
98426
98427         // Hide toolbar if no buttons to show
98428         if (hideToolbar) {
98429             me.bottomTb.hide();
98430         } else {
98431             me.bottomTb.show();
98432         }
98433     },
98434
98435     /**
98436      * Displays a new message box, or reinitializes an existing message box, based on the config options
98437      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
98438      * although those calls are basic shortcuts and do not support all of the config options allowed here.
98439      * @param {Object} config The following config options are supported: <ul>
98440      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
98441      * opens and closes (defaults to undefined)</div></li>
98442      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
98443      * <li>Ext.window.MessageBox.OK</li>
98444      * <li>Ext.window.MessageBox.YES</li>
98445      * <li>Ext.window.MessageBox.NO</li>
98446      * <li>Ext.window.MessageBox.CANCEL</li>
98447      * </ul>Or false to not show any buttons (defaults to false)</div></li>
98448      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
98449      * progress and wait dialogs will ignore this property and always hide the close button as they can only
98450      * be closed programmatically.</div></li>
98451      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
98452      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
98453      * if displayed (defaults to 75)</div></li>
98454      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
98455      * by clicking on the configured buttons, or on the dialog close button, or by pressing
98456      * the return button to enter input.
98457      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
98458      * actions and can only be closed programmatically, so any required function should be called
98459      * by the same code after it closes the dialog. Parameters passed:<ul>
98460      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
98461      * <li><tt>ok</tt></li>
98462      * <li><tt>yes</tt></li>
98463      * <li><tt>no</tt></li>
98464      * <li><tt>cancel</tt></li>
98465      * </ul></div></div></li>
98466      * <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>
98467      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
98468      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
98469      * </ul></p></div></li>
98470      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
98471      * <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
98472      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
98473      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
98474      * add an optional header icon (defaults to '')</div></li>
98475      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
98476      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
98477      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
98478      * displayed (defaults to true)</div></li>
98479      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
98480      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
98481      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
98482      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
98483      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
98484      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
98485      * <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>
98486      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
98487      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
98488      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
98489      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
98490      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)</div></li>
98491      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
98492      * </ul>
98493      * Example usage:
98494      * <pre><code>
98495 Ext.Msg.show({
98496 title: 'Address',
98497 msg: 'Please enter your address:',
98498 width: 300,
98499 buttons: Ext.Msg.OKCANCEL,
98500 multiline: true,
98501 fn: saveAddress,
98502 animateTarget: 'addAddressBtn',
98503 icon: Ext.window.MessageBox.INFO
98504 });
98505 </code></pre>
98506      * @return {Ext.window.MessageBox} this
98507      */
98508     show: function(cfg) {
98509         var me = this;
98510
98511         me.reconfigure(cfg);
98512         me.addCls(cfg.cls);
98513         if (cfg.animateTarget) {
98514             me.doAutoSize(true);
98515             me.callParent();
98516         } else {
98517             me.callParent();
98518             me.doAutoSize(true);
98519         }
98520         return me;
98521     },
98522
98523     afterShow: function(){
98524         if (this.animateTarget) {
98525             this.center();
98526         }
98527         this.callParent(arguments);
98528     },
98529
98530     doAutoSize: function(center) {
98531         var me = this,
98532             icon = me.iconComponent,
98533             iconHeight = me.iconHeight;
98534
98535         if (!Ext.isDefined(me.frameWidth)) {
98536             me.frameWidth = me.el.getWidth() - me.body.getWidth();
98537         }
98538
98539         // reset to the original dimensions
98540         icon.setHeight(iconHeight);
98541
98542         // Allow per-invocation override of minWidth
98543         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
98544
98545         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
98546         // then constraining it to within the max with. Then adding up constituent element heights.
98547         me.topContainer.doLayout();
98548         if (Ext.isIE6 || Ext.isIEQuirks) {
98549             // In IE quirks, the initial full width of the prompt fields will prevent the container element
98550             // from collapsing once sized down, so temporarily force them to a small width. They'll get
98551             // layed out to their final width later when setting the final window size.
98552             me.textField.setCalculatedSize(9);
98553             me.textArea.setCalculatedSize(9);
98554         }
98555         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
98556             height = (me.header.rendered ? me.header.getHeight() : 0) +
98557             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
98558             me.progressBar.getHeight() +
98559             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
98560
98561         // Update to the size of the content, this way the text won't wrap under the icon.
98562         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
98563         me.setSize(width + me.frameWidth, height + me.frameWidth);
98564         if (center) {
98565             me.center();
98566         }
98567         return me;
98568     },
98569
98570     updateText: function(text) {
98571         this.msg.update(text);
98572         return this.doAutoSize(true);
98573     },
98574
98575     /**
98576      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
98577      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
98578      * to clear any existing icon. This method must be called before the MessageBox is shown.
98579      * The following built-in icon classes are supported, but you can also pass in a custom class name:
98580      * <pre>
98581 Ext.window.MessageBox.INFO
98582 Ext.window.MessageBox.WARNING
98583 Ext.window.MessageBox.QUESTION
98584 Ext.window.MessageBox.ERROR
98585      *</pre>
98586      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
98587      * @return {Ext.window.MessageBox} this
98588      */
98589     setIcon : function(icon) {
98590         var me = this;
98591         me.iconComponent.removeCls(me.iconCls);
98592         if (icon) {
98593             me.iconComponent.show();
98594             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
98595             me.iconComponent.addCls(me.iconCls = icon);
98596         } else {
98597             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
98598             me.iconComponent.hide();
98599         }
98600         return me;
98601     },
98602
98603     /**
98604      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
98605      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
98606      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
98607      * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
98608      * @param {String} [progressText=''] The progress text to display inside the progress bar.
98609      * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
98610      * so that any existing body text will not get overwritten by default unless a new value is passed in)
98611      * @return {Ext.window.MessageBox} this
98612      */
98613     updateProgress : function(value, progressText, msg){
98614         this.progressBar.updateProgress(value, progressText);
98615         if (msg){
98616             this.updateText(msg);
98617         }
98618         return this;
98619     },
98620
98621     onEsc: function() {
98622         if (this.closable !== false) {
98623             this.callParent(arguments);
98624         }
98625     },
98626
98627     /**
98628      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
98629      * If a callback function is passed it will be called after the user clicks either button,
98630      * and the id of the button that was clicked will be passed as the only parameter to the callback
98631      * (could also be the top-right close button).
98632      * @param {String} title The title bar text
98633      * @param {String} msg The message box body text
98634      * @param {Function} fn (optional) The callback function invoked after the message box is closed
98635      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
98636      * @return {Ext.window.MessageBox} this
98637      */
98638     confirm: function(cfg, msg, fn, scope) {
98639         if (Ext.isString(cfg)) {
98640             cfg = {
98641                 title: cfg,
98642                 icon: 'ext-mb-question',
98643                 msg: msg,
98644                 buttons: this.YESNO,
98645                 callback: fn,
98646                 scope: scope
98647             };
98648         }
98649         return this.show(cfg);
98650     },
98651
98652     /**
98653      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
98654      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
98655      * clicks either button, and the id of the button that was clicked (could also be the top-right
98656      * close button) and the text that was entered will be passed as the two parameters to the callback.
98657      * @param {String} title The title bar text
98658      * @param {String} msg The message box body text
98659      * @param {Function} [fn] The callback function invoked after the message box is closed
98660      * @param {Object} [scope] The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
98661      * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
98662      * property, or the height in pixels to create the textbox/
98663      * @param {String} [value=''] Default value of the text input element
98664      * @return {Ext.window.MessageBox} this
98665      */
98666     prompt : function(cfg, msg, fn, scope, multiline, value){
98667         if (Ext.isString(cfg)) {
98668             cfg = {
98669                 prompt: true,
98670                 title: cfg,
98671                 minWidth: this.minPromptWidth,
98672                 msg: msg,
98673                 buttons: this.OKCANCEL,
98674                 callback: fn,
98675                 scope: scope,
98676                 multiline: multiline,
98677                 value: value
98678             };
98679         }
98680         return this.show(cfg);
98681     },
98682
98683     /**
98684      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
98685      * interaction while waiting for a long-running process to complete that does not have defined intervals.
98686      * You are responsible for closing the message box when the process is complete.
98687      * @param {String} msg The message box body text
98688      * @param {String} title (optional) The title bar text
98689      * @param {Object} config (optional) A {@link Ext.ProgressBar#wait} config object
98690      * @return {Ext.window.MessageBox} this
98691      */
98692     wait : function(cfg, title, config){
98693         if (Ext.isString(cfg)) {
98694             cfg = {
98695                 title : title,
98696                 msg : cfg,
98697                 closable: false,
98698                 wait: true,
98699                 modal: true,
98700                 minWidth: this.minProgressWidth,
98701                 waitConfig: config
98702             };
98703         }
98704         return this.show(cfg);
98705     },
98706
98707     /**
98708      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
98709      * If a callback function is passed it will be called after the user clicks the button, and the
98710      * id of the button that was clicked will be passed as the only parameter to the callback
98711      * (could also be the top-right close button).
98712      * @param {String} title The title bar text
98713      * @param {String} msg The message box body text
98714      * @param {Function} fn (optional) The callback function invoked after the message box is closed
98715      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
98716      * @return {Ext.window.MessageBox} this
98717      */
98718     alert: function(cfg, msg, fn, scope) {
98719         if (Ext.isString(cfg)) {
98720             cfg = {
98721                 title : cfg,
98722                 msg : msg,
98723                 buttons: this.OK,
98724                 fn: fn,
98725                 scope : scope,
98726                 minWidth: this.minWidth
98727             };
98728         }
98729         return this.show(cfg);
98730     },
98731
98732     /**
98733      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
98734      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
98735      * and closing the message box when the process is complete.
98736      * @param {String} title The title bar text
98737      * @param {String} msg The message box body text
98738      * @param {String} [progressText=''] The text to display inside the progress bar
98739      * @return {Ext.window.MessageBox} this
98740      */
98741     progress : function(cfg, msg, progressText){
98742         if (Ext.isString(cfg)) {
98743             cfg = {
98744                 title: cfg,
98745                 msg: msg,
98746                 progress: true,
98747                 progressText: progressText
98748             };
98749         }
98750         return this.show(cfg);
98751     }
98752 }, function() {
98753     /**
98754      * @class Ext.MessageBox
98755      * @alternateClassName Ext.Msg
98756      * @extends Ext.window.MessageBox
98757      * @singleton
98758      * Singleton instance of {@link Ext.window.MessageBox}.
98759      */
98760     Ext.MessageBox = Ext.Msg = new this();
98761 });
98762 /**
98763  * @class Ext.form.Basic
98764  * @extends Ext.util.Observable
98765  *
98766  * Provides input field management, validation, submission, and form loading services for the collection
98767  * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
98768  * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
98769  * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
98770  *
98771  * ## Form Actions
98772  *
98773  * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
98774  * See the various Action implementations for specific details of each one's functionality, as well as the
98775  * documentation for {@link #doAction} which details the configuration options that can be specified in
98776  * each action call.
98777  *
98778  * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
98779  * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
98780  * {@link #standardSubmit} config option.
98781  *
98782  * ## File uploads
98783  *
98784  * File uploads are not performed using normal 'Ajax' techniques; see the description for
98785  * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
98786  *
98787  * ## Example usage:
98788  *
98789  *     Ext.create('Ext.form.Panel', {
98790  *         title: 'Basic Form',
98791  *         renderTo: Ext.getBody(),
98792  *         bodyPadding: 5,
98793  *         width: 350,
98794  *
98795  *         // Any configuration items here will be automatically passed along to
98796  *         // the Ext.form.Basic instance when it gets created.
98797  *
98798  *         // The form will submit an AJAX request to this URL when submitted
98799  *         url: 'save-form.php',
98800  *
98801  *         items: [{
98802  *             fieldLabel: 'Field',
98803  *             name: 'theField'
98804  *         }],
98805  *
98806  *         buttons: [{
98807  *             text: 'Submit',
98808  *             handler: function() {
98809  *                 // The getForm() method returns the Ext.form.Basic instance:
98810  *                 var form = this.up('form').getForm();
98811  *                 if (form.isValid()) {
98812  *                     // Submit the Ajax request and handle the response
98813  *                     form.submit({
98814  *                         success: function(form, action) {
98815  *                            Ext.Msg.alert('Success', action.result.msg);
98816  *                         },
98817  *                         failure: function(form, action) {
98818  *                             Ext.Msg.alert('Failed', action.result.msg);
98819  *                         }
98820  *                     });
98821  *                 }
98822  *             }
98823  *         }]
98824  *     });
98825  *
98826  * @docauthor Jason Johnston <jason@sencha.com>
98827  */
98828 Ext.define('Ext.form.Basic', {
98829     extend: 'Ext.util.Observable',
98830     alternateClassName: 'Ext.form.BasicForm',
98831     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
98832                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
98833
98834     /**
98835      * Creates new form.
98836      * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
98837      * @param {Object} config Configuration options. These are normally specified in the config to the
98838      * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
98839      */
98840     constructor: function(owner, config) {
98841         var me = this,
98842             onItemAddOrRemove = me.onItemAddOrRemove;
98843
98844         /**
98845          * @property owner
98846          * @type Ext.container.Container
98847          * The container component to which this BasicForm is attached.
98848          */
98849         me.owner = owner;
98850
98851         // Listen for addition/removal of fields in the owner container
98852         me.mon(owner, {
98853             add: onItemAddOrRemove,
98854             remove: onItemAddOrRemove,
98855             scope: me
98856         });
98857
98858         Ext.apply(me, config);
98859
98860         // Normalize the paramOrder to an Array
98861         if (Ext.isString(me.paramOrder)) {
98862             me.paramOrder = me.paramOrder.split(/[\s,|]/);
98863         }
98864
98865         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
98866
98867         me.addEvents(
98868             /**
98869              * @event beforeaction
98870              * Fires before any action is performed. Return false to cancel the action.
98871              * @param {Ext.form.Basic} this
98872              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
98873              */
98874             'beforeaction',
98875             /**
98876              * @event actionfailed
98877              * Fires when an action fails.
98878              * @param {Ext.form.Basic} this
98879              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
98880              */
98881             'actionfailed',
98882             /**
98883              * @event actioncomplete
98884              * Fires when an action is completed.
98885              * @param {Ext.form.Basic} this
98886              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
98887              */
98888             'actioncomplete',
98889             /**
98890              * @event validitychange
98891              * Fires when the validity of the entire form changes.
98892              * @param {Ext.form.Basic} this
98893              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
98894              */
98895             'validitychange',
98896             /**
98897              * @event dirtychange
98898              * Fires when the dirty state of the entire form changes.
98899              * @param {Ext.form.Basic} this
98900              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
98901              */
98902             'dirtychange'
98903         );
98904         me.callParent();
98905     },
98906
98907     /**
98908      * Do any post constructor initialization
98909      * @private
98910      */
98911     initialize: function(){
98912         this.initialized = true;
98913         this.onValidityChange(!this.hasInvalidField());
98914     },
98915
98916     /**
98917      * @cfg {String} method
98918      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
98919      */
98920
98921     /**
98922      * @cfg {Ext.data.reader.Reader} reader
98923      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
98924      * data when executing 'load' actions. This is optional as there is built-in
98925      * support for processing JSON responses.
98926      */
98927
98928     /**
98929      * @cfg {Ext.data.reader.Reader} errorReader
98930      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
98931      * read field error messages returned from 'submit' actions. This is optional
98932      * as there is built-in support for processing JSON responses.</p>
98933      * <p>The Records which provide messages for the invalid Fields must use the
98934      * Field name (or id) as the Record ID, and must contain a field called 'msg'
98935      * which contains the error message.</p>
98936      * <p>The errorReader does not have to be a full-blown implementation of a
98937      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
98938      * which returns an Array of Records in an object with the following
98939      * structure:</p><pre><code>
98940 {
98941     records: recordArray
98942 }
98943 </code></pre>
98944      */
98945
98946     /**
98947      * @cfg {String} url
98948      * The URL to use for form actions if one isn't supplied in the
98949      * {@link #doAction doAction} options.
98950      */
98951
98952     /**
98953      * @cfg {Object} baseParams
98954      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
98955      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.</p>
98956      */
98957
98958     /**
98959      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
98960      */
98961     timeout: 30,
98962
98963     /**
98964      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
98965      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
98966      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
98967      * forms.
98968      * Such as the following:<pre><code>
98969 api: {
98970     load: App.ss.MyProfile.load,
98971     submit: App.ss.MyProfile.submit
98972 }
98973 </code></pre>
98974      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
98975      * to customize how the load method is invoked.
98976      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
98977      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
98978      */
98979
98980     /**
98981      * @cfg {String/String[]} paramOrder <p>A list of params to be executed server side.
98982      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
98983      * <code>load</code> configuration.</p>
98984      * <p>Specify the params in the order in which they must be executed on the
98985      * server-side as either (1) an Array of String values, or (2) a String of params
98986      * delimited by either whitespace, comma, or pipe. For example,
98987      * any of the following would be acceptable:</p><pre><code>
98988 paramOrder: ['param1','param2','param3']
98989 paramOrder: 'param1 param2 param3'
98990 paramOrder: 'param1,param2,param3'
98991 paramOrder: 'param1|param2|param'
98992      </code></pre>
98993      */
98994
98995     /**
98996      * @cfg {Boolean} paramsAsHash
98997      * Only used for the <code>{@link #api}</code>
98998      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
98999      * single hash collection of named arguments. Providing a
99000      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
99001      */
99002     paramsAsHash: false,
99003
99004     /**
99005      * @cfg {String} waitTitle
99006      * The default title to show for the waiting message box
99007      */
99008     waitTitle: 'Please Wait...',
99009
99010     /**
99011      * @cfg {Boolean} trackResetOnLoad
99012      * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
99013      * when the form was first created.
99014      */
99015     trackResetOnLoad: false,
99016
99017     /**
99018      * @cfg {Boolean} standardSubmit
99019      * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
99020      * All of the field values, plus any additional params configured via {@link #baseParams}
99021      * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
99022      */
99023
99024     /**
99025      * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
99026      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
99027      * element by passing it or its id or mask the form itself by passing in true.
99028      */
99029
99030
99031     // Private
99032     wasDirty: false,
99033
99034
99035     /**
99036      * Destroys this object.
99037      */
99038     destroy: function() {
99039         this.clearListeners();
99040         this.checkValidityTask.cancel();
99041     },
99042
99043     /**
99044      * @private
99045      * Handle addition or removal of descendant items. Invalidates the cached list of fields
99046      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
99047      * for state change events on added fields, and tracks components with formBind=true.
99048      */
99049     onItemAddOrRemove: function(parent, child) {
99050         var me = this,
99051             isAdding = !!child.ownerCt,
99052             isContainer = child.isContainer;
99053
99054         function handleField(field) {
99055             // Listen for state change events on fields
99056             me[isAdding ? 'mon' : 'mun'](field, {
99057                 validitychange: me.checkValidity,
99058                 dirtychange: me.checkDirty,
99059                 scope: me,
99060                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
99061             });
99062             // Flush the cached list of fields
99063             delete me._fields;
99064         }
99065
99066         if (child.isFormField) {
99067             handleField(child);
99068         } else if (isContainer) {
99069             // Walk down
99070             if (child.isDestroyed) {
99071                 // the container is destroyed, this means we may have child fields, so here
99072                 // we just invalidate all the fields to be sure.
99073                 delete me._fields;
99074             } else {
99075                 Ext.Array.forEach(child.query('[isFormField]'), handleField);
99076             }
99077         }
99078
99079         // Flush the cached list of formBind components
99080         delete this._boundItems;
99081
99082         // Check form bind, but only after initial add. Batch it to prevent excessive validation
99083         // calls when many fields are being added at once.
99084         if (me.initialized) {
99085             me.checkValidityTask.delay(10);
99086         }
99087     },
99088
99089     /**
99090      * Return all the {@link Ext.form.field.Field} components in the owner container.
99091      * @return {Ext.util.MixedCollection} Collection of the Field objects
99092      */
99093     getFields: function() {
99094         var fields = this._fields;
99095         if (!fields) {
99096             fields = this._fields = Ext.create('Ext.util.MixedCollection');
99097             fields.addAll(this.owner.query('[isFormField]'));
99098         }
99099         return fields;
99100     },
99101
99102     /**
99103      * @private
99104      * Finds and returns the set of all items bound to fields inside this form
99105      * @return {Ext.util.MixedCollection} The set of all bound form field items
99106      */
99107     getBoundItems: function() {
99108         var boundItems = this._boundItems;
99109         
99110         if (!boundItems || boundItems.getCount() === 0) {
99111             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
99112             boundItems.addAll(this.owner.query('[formBind]'));
99113         }
99114         
99115         return boundItems;
99116     },
99117
99118     /**
99119      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
99120      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
99121      */
99122     hasInvalidField: function() {
99123         return !!this.getFields().findBy(function(field) {
99124             var preventMark = field.preventMark,
99125                 isValid;
99126             field.preventMark = true;
99127             isValid = field.isValid();
99128             field.preventMark = preventMark;
99129             return !isValid;
99130         });
99131     },
99132
99133     /**
99134      * Returns true if client-side validation on the form is successful. Any invalid fields will be
99135      * marked as invalid. If you only want to determine overall form validity without marking anything,
99136      * use {@link #hasInvalidField} instead.
99137      * @return Boolean
99138      */
99139     isValid: function() {
99140         var me = this,
99141             invalid;
99142         me.batchLayouts(function() {
99143             invalid = me.getFields().filterBy(function(field) {
99144                 return !field.validate();
99145             });
99146         });
99147         return invalid.length < 1;
99148     },
99149
99150     /**
99151      * Check whether the validity of the entire form has changed since it was last checked, and
99152      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
99153      * when an individual field's validity changes.
99154      */
99155     checkValidity: function() {
99156         var me = this,
99157             valid = !me.hasInvalidField();
99158         if (valid !== me.wasValid) {
99159             me.onValidityChange(valid);
99160             me.fireEvent('validitychange', me, valid);
99161             me.wasValid = valid;
99162         }
99163     },
99164
99165     /**
99166      * @private
99167      * Handle changes in the form's validity. If there are any sub components with
99168      * formBind=true then they are enabled/disabled based on the new validity.
99169      * @param {Boolean} valid
99170      */
99171     onValidityChange: function(valid) {
99172         var boundItems = this.getBoundItems();
99173         if (boundItems) {
99174             boundItems.each(function(cmp) {
99175                 if (cmp.disabled === valid) {
99176                     cmp.setDisabled(!valid);
99177                 }
99178             });
99179         }
99180     },
99181
99182     /**
99183      * <p>Returns true if any fields in this form have changed from their original values.</p>
99184      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
99185      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
99186      * or {@link #loadRecord}.</p>
99187      * @return Boolean
99188      */
99189     isDirty: function() {
99190         return !!this.getFields().findBy(function(f) {
99191             return f.isDirty();
99192         });
99193     },
99194
99195     /**
99196      * Check whether the dirty state of the entire form has changed since it was last checked, and
99197      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
99198      * when an individual field's dirty state changes.
99199      */
99200     checkDirty: function() {
99201         var dirty = this.isDirty();
99202         if (dirty !== this.wasDirty) {
99203             this.fireEvent('dirtychange', this, dirty);
99204             this.wasDirty = dirty;
99205         }
99206     },
99207
99208     /**
99209      * <p>Returns true if the form contains a file upload field. This is used to determine the
99210      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
99211      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
99212      * element containing all the fields is created temporarily and submitted with its
99213      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
99214      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
99215      * but removed after the return data has been gathered.</p>
99216      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
99217      * server is using JSON to send the return object, then the
99218      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
99219      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
99220      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
99221      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
99222      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
99223      * is created containing a <tt>responseText</tt> property in order to conform to the
99224      * requirements of event handlers and callbacks.</p>
99225      * <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>
99226      * and some server technologies (notably JEE) may require some custom processing in order to
99227      * retrieve parameter names and parameter values from the packet content.</p>
99228      * @return Boolean
99229      */
99230     hasUpload: function() {
99231         return !!this.getFields().findBy(function(f) {
99232             return f.isFileUpload();
99233         });
99234     },
99235
99236     /**
99237      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
99238      * to perform application-specific processing.
99239      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
99240      * or instance of {@link Ext.form.action.Action} to perform.
99241      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
99242      * that will get created, if the <tt>action</tt> argument is a String.
99243      * <p>All of the config options listed below are supported by both the
99244      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
99245      * actions unless otherwise noted (custom actions could also accept
99246      * other config options):</p><ul>
99247      *
99248      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
99249      * to the form's {@link #url}.)</div></li>
99250      *
99251      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
99252      * to the form's method, or POST if not defined)</div></li>
99253      *
99254      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
99255      * (defaults to the form's baseParams, or none if not defined)</p>
99256      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
99257      *
99258      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
99259      *
99260      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
99261      * be invoked after a successful response (see top of
99262      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
99263      * for a description of what constitutes a successful response).
99264      * The function is passed the following parameters:<ul>
99265      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
99266      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
99267      * <div class="sub-desc">The action object contains these properties of interest:<ul>
99268      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
99269      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
99270      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
99271      * </ul></div></li></ul></div></li>
99272      *
99273      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
99274      * failed transaction attempt. The function is passed the following parameters:<ul>
99275      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
99276      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
99277      * <div class="sub-desc">The action object contains these properties of interest:<ul>
99278      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
99279      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
99280      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
99281      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
99282      * </ul></div></li></ul></div></li>
99283      *
99284      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
99285      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
99286      *
99287      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
99288      * Determines whether a Form's fields are validated in a final call to
99289      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
99290      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
99291      *
99292      * @return {Ext.form.Basic} this
99293      */
99294     doAction: function(action, options) {
99295         if (Ext.isString(action)) {
99296             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
99297         }
99298         if (this.fireEvent('beforeaction', this, action) !== false) {
99299             this.beforeAction(action);
99300             Ext.defer(action.run, 100, action);
99301         }
99302         return this;
99303     },
99304
99305     /**
99306      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
99307      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config is
99308      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
99309      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
99310      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
99311      * <p>The following code:</p><pre><code>
99312 myFormPanel.getForm().submit({
99313     clientValidation: true,
99314     url: 'updateConsignment.php',
99315     params: {
99316         newStatus: 'delivered'
99317     },
99318     success: function(form, action) {
99319        Ext.Msg.alert('Success', action.result.msg);
99320     },
99321     failure: function(form, action) {
99322         switch (action.failureType) {
99323             case Ext.form.action.Action.CLIENT_INVALID:
99324                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
99325                 break;
99326             case Ext.form.action.Action.CONNECT_FAILURE:
99327                 Ext.Msg.alert('Failure', 'Ajax communication failed');
99328                 break;
99329             case Ext.form.action.Action.SERVER_INVALID:
99330                Ext.Msg.alert('Failure', action.result.msg);
99331        }
99332     }
99333 });
99334 </code></pre>
99335      * would process the following server response for a successful submission:<pre><code>
99336 {
99337     "success":true, // note this is Boolean, not string
99338     "msg":"Consignment updated"
99339 }
99340 </code></pre>
99341      * and the following server response for a failed submission:<pre><code>
99342 {
99343     "success":false, // note this is Boolean, not string
99344     "msg":"You do not have permission to perform this operation"
99345 }
99346 </code></pre>
99347      * @return {Ext.form.Basic} this
99348      */
99349     submit: function(options) {
99350         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
99351     },
99352
99353     /**
99354      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
99355      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
99356      * @return {Ext.form.Basic} this
99357      */
99358     load: function(options) {
99359         return this.doAction(this.api ? 'directload' : 'load', options);
99360     },
99361
99362     /**
99363      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
99364      * @param {Ext.data.Model} record The record to edit
99365      * @return {Ext.form.Basic} this
99366      */
99367     updateRecord: function(record) {
99368         var fields = record.fields,
99369             values = this.getFieldValues(),
99370             name,
99371             obj = {};
99372
99373         fields.each(function(f) {
99374             name = f.name;
99375             if (name in values) {
99376                 obj[name] = values[name];
99377             }
99378         });
99379
99380         record.beginEdit();
99381         record.set(obj);
99382         record.endEdit();
99383
99384         return this;
99385     },
99386
99387     /**
99388      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
99389      * {@link Ext.data.Model#raw record data}.
99390      * See also {@link #trackResetOnLoad}.
99391      * @param {Ext.data.Model} record The record to load
99392      * @return {Ext.form.Basic} this
99393      */
99394     loadRecord: function(record) {
99395         this._record = record;
99396         return this.setValues(record.data);
99397     },
99398
99399     /**
99400      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
99401      * @return {Ext.data.Model} The record
99402      */
99403     getRecord: function() {
99404         return this._record;
99405     },
99406
99407     /**
99408      * @private
99409      * Called before an action is performed via {@link #doAction}.
99410      * @param {Ext.form.action.Action} action The Action instance that was invoked
99411      */
99412     beforeAction: function(action) {
99413         var waitMsg = action.waitMsg,
99414             maskCls = Ext.baseCSSPrefix + 'mask-loading',
99415             waitMsgTarget;
99416
99417         // Call HtmlEditor's syncValue before actions
99418         this.getFields().each(function(f) {
99419             if (f.isFormField && f.syncValue) {
99420                 f.syncValue();
99421             }
99422         });
99423
99424         if (waitMsg) {
99425             waitMsgTarget = this.waitMsgTarget;
99426             if (waitMsgTarget === true) {
99427                 this.owner.el.mask(waitMsg, maskCls);
99428             } else if (waitMsgTarget) {
99429                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
99430                 waitMsgTarget.mask(waitMsg, maskCls);
99431             } else {
99432                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
99433             }
99434         }
99435     },
99436
99437     /**
99438      * @private
99439      * Called after an action is performed via {@link #doAction}.
99440      * @param {Ext.form.action.Action} action The Action instance that was invoked
99441      * @param {Boolean} success True if the action completed successfully, false, otherwise.
99442      */
99443     afterAction: function(action, success) {
99444         if (action.waitMsg) {
99445             var MessageBox = Ext.MessageBox,
99446                 waitMsgTarget = this.waitMsgTarget;
99447             if (waitMsgTarget === true) {
99448                 this.owner.el.unmask();
99449             } else if (waitMsgTarget) {
99450                 waitMsgTarget.unmask();
99451             } else {
99452                 MessageBox.updateProgress(1);
99453                 MessageBox.hide();
99454             }
99455         }
99456         if (success) {
99457             if (action.reset) {
99458                 this.reset();
99459             }
99460             Ext.callback(action.success, action.scope || action, [this, action]);
99461             this.fireEvent('actioncomplete', this, action);
99462         } else {
99463             Ext.callback(action.failure, action.scope || action, [this, action]);
99464             this.fireEvent('actionfailed', this, action);
99465         }
99466     },
99467
99468
99469     /**
99470      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
99471      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
99472      * {@link Ext.form.field.Field#getName name or hiddenName}).
99473      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
99474      */
99475     findField: function(id) {
99476         return this.getFields().findBy(function(f) {
99477             return f.id === id || f.getName() === id;
99478         });
99479     },
99480
99481
99482     /**
99483      * Mark fields in this form invalid in bulk.
99484      * @param {Object/Object[]/Ext.data.Errors} errors
99485      * Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
99486      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
99487      * @return {Ext.form.Basic} this
99488      */
99489     markInvalid: function(errors) {
99490         var me = this;
99491
99492         function mark(fieldId, msg) {
99493             var field = me.findField(fieldId);
99494             if (field) {
99495                 field.markInvalid(msg);
99496             }
99497         }
99498
99499         if (Ext.isArray(errors)) {
99500             Ext.each(errors, function(err) {
99501                 mark(err.id, err.msg);
99502             });
99503         }
99504         else if (errors instanceof Ext.data.Errors) {
99505             errors.each(function(err) {
99506                 mark(err.field, err.message);
99507             });
99508         }
99509         else {
99510             Ext.iterate(errors, mark);
99511         }
99512         return this;
99513     },
99514
99515     /**
99516      * Set values for fields in this form in bulk.
99517      * @param {Object/Object[]} values Either an array in the form:<pre><code>
99518 [{id:'clientName', value:'Fred. Olsen Lines'},
99519  {id:'portOfLoading', value:'FXT'},
99520  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
99521      * or an object hash of the form:<pre><code>
99522 {
99523     clientName: 'Fred. Olsen Lines',
99524     portOfLoading: 'FXT',
99525     portOfDischarge: 'OSL'
99526 }</code></pre>
99527      * @return {Ext.form.Basic} this
99528      */
99529     setValues: function(values) {
99530         var me = this;
99531
99532         function setVal(fieldId, val) {
99533             var field = me.findField(fieldId);
99534             if (field) {
99535                 field.setValue(val);
99536                 if (me.trackResetOnLoad) {
99537                     field.resetOriginalValue();
99538                 }
99539             }
99540         }
99541
99542         if (Ext.isArray(values)) {
99543             // array of objects
99544             Ext.each(values, function(val) {
99545                 setVal(val.id, val.value);
99546             });
99547         } else {
99548             // object hash
99549             Ext.iterate(values, setVal);
99550         }
99551         return this;
99552     },
99553
99554     /**
99555      * Retrieves the fields in the form as a set of key/value pairs, using their
99556      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
99557      * If multiple fields return values under the same name those values will be combined into an Array.
99558      * This is similar to {@link #getFieldValues} except that this method collects only String values for
99559      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
99560      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
99561      * URL-encoded param string. Defaults to false.
99562      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
99563      * Defaults to false.
99564      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
99565      * Defaults to false.
99566      * @return {String/Object}
99567      */
99568     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
99569         var values = {};
99570
99571         this.getFields().each(function(field) {
99572             if (!dirtyOnly || field.isDirty()) {
99573                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
99574                 if (Ext.isObject(data)) {
99575                     Ext.iterate(data, function(name, val) {
99576                         if (includeEmptyText && val === '') {
99577                             val = field.emptyText || '';
99578                         }
99579                         if (name in values) {
99580                             var bucket = values[name],
99581                                 isArray = Ext.isArray;
99582                             if (!isArray(bucket)) {
99583                                 bucket = values[name] = [bucket];
99584                             }
99585                             if (isArray(val)) {
99586                                 values[name] = bucket.concat(val);
99587                             } else {
99588                                 bucket.push(val);
99589                             }
99590                         } else {
99591                             values[name] = val;
99592                         }
99593                     });
99594                 }
99595             }
99596         });
99597
99598         if (asString) {
99599             values = Ext.Object.toQueryString(values);
99600         }
99601         return values;
99602     },
99603
99604     /**
99605      * Retrieves the fields in the form as a set of key/value pairs, using their
99606      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
99607      * If multiple fields return values under the same name those values will be combined into an Array.
99608      * This is similar to {@link #getValues} except that this method collects type-specific data values
99609      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
99610      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
99611      * Defaults to false.
99612      * @return {Object}
99613      */
99614     getFieldValues: function(dirtyOnly) {
99615         return this.getValues(false, dirtyOnly, false, true);
99616     },
99617
99618     /**
99619      * Clears all invalid field messages in this form.
99620      * @return {Ext.form.Basic} this
99621      */
99622     clearInvalid: function() {
99623         var me = this;
99624         me.batchLayouts(function() {
99625             me.getFields().each(function(f) {
99626                 f.clearInvalid();
99627             });
99628         });
99629         return me;
99630     },
99631
99632     /**
99633      * Resets all fields in this form.
99634      * @return {Ext.form.Basic} this
99635      */
99636     reset: function() {
99637         var me = this;
99638         me.batchLayouts(function() {
99639             me.getFields().each(function(f) {
99640                 f.reset();
99641             });
99642         });
99643         return me;
99644     },
99645
99646     /**
99647      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
99648      * @param {Object} obj The object to be applied
99649      * @return {Ext.form.Basic} this
99650      */
99651     applyToFields: function(obj) {
99652         this.getFields().each(function(f) {
99653             Ext.apply(f, obj);
99654         });
99655         return this;
99656     },
99657
99658     /**
99659      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
99660      * @param {Object} obj The object to be applied
99661      * @return {Ext.form.Basic} this
99662      */
99663     applyIfToFields: function(obj) {
99664         this.getFields().each(function(f) {
99665             Ext.applyIf(f, obj);
99666         });
99667         return this;
99668     },
99669
99670     /**
99671      * @private
99672      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
99673      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
99674      * @param {Function} fn
99675      */
99676     batchLayouts: function(fn) {
99677         var me = this,
99678             suspended = new Ext.util.HashMap();
99679
99680         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
99681         me.getFields().each(function(field) {
99682             var ownerCt = field.ownerCt;
99683             if (!suspended.contains(ownerCt)) {
99684                 suspended.add(ownerCt);
99685                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
99686                 ownerCt.suspendLayout = true;
99687             }
99688         });
99689
99690         // Invoke the function
99691         fn();
99692
99693         // Un-suspend the container layouts
99694         suspended.each(function(id, ct) {
99695             ct.suspendLayout = ct.oldSuspendLayout;
99696             delete ct.oldSuspendLayout;
99697         });
99698
99699         // Trigger a single layout
99700         me.owner.doComponentLayout();
99701     }
99702 });
99703
99704 /**
99705  * @class Ext.form.FieldAncestor
99706
99707 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
99708 items subtree. Adds the following capabilities:
99709
99710 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
99711   instances at any depth within the container.
99712 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
99713   of individual fields at the container level.
99714 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
99715   container, to facilitate uniform configuration of all fields.
99716
99717 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
99718 and should not normally need to be used directly.
99719
99720  * @markdown
99721  * @docauthor Jason Johnston <jason@sencha.com>
99722  */
99723 Ext.define('Ext.form.FieldAncestor', {
99724
99725     /**
99726      * @cfg {Object} fieldDefaults
99727      * <p>If specified, the properties in this object are used as default config values for each
99728      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
99729      * that is added as a descendant of this container. Corresponding values specified in an individual field's
99730      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
99731      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
99732      * options may be specified in the <tt>fieldDefaults</tt>.</p>
99733      * <p>Example:</p>
99734      * <pre><code>new Ext.form.Panel({
99735     fieldDefaults: {
99736         labelAlign: 'left',
99737         labelWidth: 100
99738     },
99739     items: [{
99740         xtype: 'fieldset',
99741         defaults: {
99742             labelAlign: 'top'
99743         },
99744         items: [{
99745             name: 'field1'
99746         }, {
99747             name: 'field2'
99748         }]
99749     }, {
99750         xtype: 'fieldset',
99751         items: [{
99752             name: 'field3',
99753             labelWidth: 150
99754         }, {
99755             name: 'field4'
99756         }]
99757     }]
99758 });</code></pre>
99759      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
99760      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
99761      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
99762      */
99763
99764
99765     /**
99766      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
99767      * of any components importing this mixin.
99768      */
99769     initFieldAncestor: function() {
99770         var me = this,
99771             onSubtreeChange = me.onFieldAncestorSubtreeChange;
99772
99773         me.addEvents(
99774             /**
99775              * @event fieldvaliditychange
99776              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
99777              * container changes.
99778              * @param {Ext.form.FieldAncestor} this
99779              * @param {Ext.form.Labelable} The Field instance whose validity changed
99780              * @param {String} isValid The field's new validity state
99781              */
99782             'fieldvaliditychange',
99783
99784             /**
99785              * @event fielderrorchange
99786              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
99787              * instances within this container.
99788              * @param {Ext.form.FieldAncestor} this
99789              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
99790              * @param {String} error The active error message
99791              */
99792             'fielderrorchange'
99793         );
99794
99795         // Catch addition and removal of descendant fields
99796         me.on('add', onSubtreeChange, me);
99797         me.on('remove', onSubtreeChange, me);
99798
99799         me.initFieldDefaults();
99800     },
99801
99802     /**
99803      * @private Initialize the {@link #fieldDefaults} object
99804      */
99805     initFieldDefaults: function() {
99806         if (!this.fieldDefaults) {
99807             this.fieldDefaults = {};
99808         }
99809     },
99810
99811     /**
99812      * @private
99813      * Handle the addition and removal of components in the FieldAncestor component's child tree.
99814      */
99815     onFieldAncestorSubtreeChange: function(parent, child) {
99816         var me = this,
99817             isAdding = !!child.ownerCt;
99818
99819         function handleCmp(cmp) {
99820             var isLabelable = cmp.isFieldLabelable,
99821                 isField = cmp.isFormField;
99822             if (isLabelable || isField) {
99823                 if (isLabelable) {
99824                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
99825                 }
99826                 if (isField) {
99827                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
99828                 }
99829             }
99830             else if (cmp.isContainer) {
99831                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
99832             }
99833         }
99834         handleCmp(child);
99835     },
99836
99837     /**
99838      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99839      * @param {Ext.form.Labelable} labelable The instance that was added
99840      */
99841     onLabelableAdded: function(labelable) {
99842         var me = this;
99843
99844         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
99845         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
99846
99847         labelable.setFieldDefaults(me.fieldDefaults);
99848     },
99849
99850     /**
99851      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
99852      * @param {Ext.form.field.Field} field The field which was added
99853      */
99854     onFieldAdded: function(field) {
99855         var me = this;
99856         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
99857     },
99858
99859     /**
99860      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99861      * @param {Ext.form.Labelable} labelable The instance that was removed
99862      */
99863     onLabelableRemoved: function(labelable) {
99864         var me = this;
99865         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
99866     },
99867
99868     /**
99869      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
99870      * @param {Ext.form.field.Field} field The field which was removed
99871      */
99872     onFieldRemoved: function(field) {
99873         var me = this;
99874         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
99875     },
99876
99877     /**
99878      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
99879      */
99880     handleFieldValidityChange: function(field, isValid) {
99881         var me = this;
99882         me.fireEvent('fieldvaliditychange', me, field, isValid);
99883         me.onFieldValidityChange();
99884     },
99885
99886     /**
99887      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
99888      */
99889     handleFieldErrorChange: function(labelable, activeError) {
99890         var me = this;
99891         me.fireEvent('fielderrorchange', me, labelable, activeError);
99892         me.onFieldErrorChange();
99893     },
99894
99895     /**
99896      * @protected Fired when the validity of any field within the container changes.
99897      * @param {Ext.form.field.Field} The sub-field whose validity changed
99898      * @param {String} The new validity state
99899      */
99900     onFieldValidityChange: Ext.emptyFn,
99901
99902     /**
99903      * @protected Fired when the error message of any field within the container changes.
99904      * @param {Ext.form.Labelable} The sub-field whose active error changed
99905      * @param {String} The new active error message
99906      */
99907     onFieldErrorChange: Ext.emptyFn
99908
99909 });
99910 /**
99911  * @class Ext.layout.container.CheckboxGroup
99912  * @extends Ext.layout.container.Container
99913  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
99914  * It groups the component's sub-items into columns based on the component's
99915  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
99916  *
99917  */
99918 Ext.define('Ext.layout.container.CheckboxGroup', {
99919     extend: 'Ext.layout.container.Container',
99920     alias: ['layout.checkboxgroup'],
99921
99922
99923     onLayout: function() {
99924         var numCols = this.getColCount(),
99925             shadowCt = this.getShadowCt(),
99926             owner = this.owner,
99927             items = owner.items,
99928             shadowItems = shadowCt.items,
99929             numItems = items.length,
99930             colIndex = 0,
99931             i, numRows;
99932
99933         // Distribute the items into the appropriate column containers. We add directly to the
99934         // containers' items collection rather than calling container.add(), because we need the
99935         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
99936         // in case items have been added, removed, or reordered.
99937
99938         shadowItems.each(function(col) {
99939             col.items.clear();
99940         });
99941
99942         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
99943         // from the CheckboxGroup; adjust to match.
99944         while (shadowItems.length > numCols) {
99945             shadowCt.remove(shadowItems.last());
99946         }
99947         while (shadowItems.length < numCols) {
99948             shadowCt.add({
99949                 xtype: 'container',
99950                 cls: owner.groupCls,
99951                 flex: 1
99952             });
99953         }
99954
99955         if (owner.vertical) {
99956             numRows = Math.ceil(numItems / numCols);
99957             for (i = 0; i < numItems; i++) {
99958                 if (i > 0 && i % numRows === 0) {
99959                     colIndex++;
99960                 }
99961                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99962             }
99963         } else {
99964             for (i = 0; i < numItems; i++) {
99965                 colIndex = i % numCols;
99966                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99967             }
99968         }
99969
99970         if (!shadowCt.rendered) {
99971             shadowCt.render(this.getRenderTarget());
99972         } else {
99973             // Ensure all items are rendered in the correct place in the correct column - this won't
99974             // get done by the column containers themselves if their dimensions are not changing.
99975             shadowItems.each(function(col) {
99976                 var layout = col.getLayout();
99977                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
99978             });
99979         }
99980
99981         shadowCt.doComponentLayout();
99982     },
99983
99984
99985     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
99986     renderItems: Ext.emptyFn,
99987
99988
99989     /**
99990      * @private
99991      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
99992      * into columns.
99993      */
99994     getShadowCt: function() {
99995         var me = this,
99996             shadowCt = me.shadowCt,
99997             owner, items, item, columns, columnsIsArray, numCols, i;
99998
99999         if (!shadowCt) {
100000             // Create the column containers based on the owner's 'columns' config
100001             owner = me.owner;
100002             columns = owner.columns;
100003             columnsIsArray = Ext.isArray(columns);
100004             numCols = me.getColCount();
100005             items = [];
100006             for(i = 0; i < numCols; i++) {
100007                 item = {
100008                     xtype: 'container',
100009                     cls: owner.groupCls
100010                 };
100011                 if (columnsIsArray) {
100012                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
100013                     // numbers, used as relative flex values.
100014                     if (columns[i] < 1) {
100015                         item.flex = columns[i];
100016                     } else {
100017                         item.width = columns[i];
100018                     }
100019                 }
100020                 else {
100021                     // All columns the same width
100022                     item.flex = 1;
100023                 }
100024                 items.push(item);
100025             }
100026
100027             // Create the shadow container; delay rendering until after items are added to the columns
100028             shadowCt = me.shadowCt = Ext.createWidget('container', {
100029                 layout: 'hbox',
100030                 items: items,
100031                 ownerCt: owner
100032             });
100033         }
100034         
100035         return shadowCt;
100036     },
100037
100038
100039     /**
100040      * @private Get the number of columns in the checkbox group
100041      */
100042     getColCount: function() {
100043         var owner = this.owner,
100044             colsCfg = owner.columns;
100045         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
100046     }
100047
100048 });
100049
100050 /**
100051  * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
100052  * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
100053  * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
100054  * This is useful for arranging a group of fields or other components within a single item in a form, so
100055  * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
100056  * a single label in a form.
100057  * 
100058  * The container's configured {@link #items} will be layed out within the field body area according to the
100059  * configured {@link #layout} type. The default layout is `'autocontainer'`.
100060  * 
100061  * Like regular fields, FieldContainer can inherit its decoration configuration from the
100062  * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
100063  * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
100064  * it may itself contain.
100065  * 
100066  * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
100067  * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
100068  * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
100069  *
100070  * # Example
100071  * 
100072  *     @example
100073  *     Ext.create('Ext.form.Panel', {
100074  *         title: 'FieldContainer Example',
100075  *         width: 550,
100076  *         bodyPadding: 10,
100077  * 
100078  *         items: [{
100079  *             xtype: 'fieldcontainer',
100080  *             fieldLabel: 'Last Three Jobs',
100081  *             labelWidth: 100,
100082  * 
100083  *             // The body area will contain three text fields, arranged
100084  *             // horizontally, separated by draggable splitters.
100085  *             layout: 'hbox',
100086  *             items: [{
100087  *                 xtype: 'textfield',
100088  *                 flex: 1
100089  *             }, {
100090  *                 xtype: 'splitter'
100091  *             }, {
100092  *                 xtype: 'textfield',
100093  *                 flex: 1
100094  *             }, {
100095  *                 xtype: 'splitter'
100096  *             }, {
100097  *                 xtype: 'textfield',
100098  *                 flex: 1
100099  *             }]
100100  *         }],
100101  *         renderTo: Ext.getBody()
100102  *     });
100103  * 
100104  * # Usage of fieldDefaults
100105  *
100106  *     @example
100107  *     Ext.create('Ext.form.Panel', {
100108  *         title: 'FieldContainer Example',
100109  *         width: 350,
100110  *         bodyPadding: 10,
100111  * 
100112  *         items: [{
100113  *             xtype: 'fieldcontainer',
100114  *             fieldLabel: 'Your Name',
100115  *             labelWidth: 75,
100116  *             defaultType: 'textfield',
100117  * 
100118  *             // Arrange fields vertically, stretched to full width
100119  *             layout: 'anchor',
100120  *             defaults: {
100121  *                 layout: '100%'
100122  *             },
100123  * 
100124  *             // These config values will be applied to both sub-fields, except
100125  *             // for Last Name which will use its own msgTarget.
100126  *             fieldDefaults: {
100127  *                 msgTarget: 'under',
100128  *                 labelAlign: 'top'
100129  *             },
100130  * 
100131  *             items: [{
100132  *                 fieldLabel: 'First Name',
100133  *                 name: 'firstName'
100134  *             }, {
100135  *                 fieldLabel: 'Last Name',
100136  *                 name: 'lastName',
100137  *                 msgTarget: 'under'
100138  *             }]
100139  *         }],
100140  *         renderTo: Ext.getBody()
100141  *     });
100142  * 
100143  * @docauthor Jason Johnston <jason@sencha.com>
100144  */
100145 Ext.define('Ext.form.FieldContainer', {
100146     extend: 'Ext.container.Container',
100147     mixins: {
100148         labelable: 'Ext.form.Labelable',
100149         fieldAncestor: 'Ext.form.FieldAncestor'
100150     },
100151     alias: 'widget.fieldcontainer',
100152
100153     componentLayout: 'field',
100154
100155     /**
100156      * @cfg {Boolean} combineLabels
100157      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
100158      * generate its label by combining the labels of all the fields it contains. Defaults to false.
100159      */
100160     combineLabels: false,
100161
100162     /**
100163      * @cfg {String} labelConnector
100164      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
100165      * set to true. Defaults to ', '.
100166      */
100167     labelConnector: ', ',
100168
100169     /**
100170      * @cfg {Boolean} combineErrors
100171      * If set to true, the field container will automatically combine and display the validation errors from
100172      * all the fields it contains as a single error on the container, according to the configured
100173      * {@link #msgTarget}. Defaults to false.
100174      */
100175     combineErrors: false,
100176
100177     maskOnDisable: false,
100178
100179     initComponent: function() {
100180         var me = this,
100181             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
100182
100183         // Init mixins
100184         me.initLabelable();
100185         me.initFieldAncestor();
100186
100187         me.callParent();
100188     },
100189
100190     /**
100191      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
100192      * @param {Ext.form.Labelable} labelable The instance that was added
100193      */
100194     onLabelableAdded: function(labelable) {
100195         var me = this;
100196         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
100197         me.updateLabel();
100198     },
100199
100200     /**
100201      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
100202      * @param {Ext.form.Labelable} labelable The instance that was removed
100203      */
100204     onLabelableRemoved: function(labelable) {
100205         var me = this;
100206         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
100207         me.updateLabel();
100208     },
100209
100210     onRender: function() {
100211         var me = this;
100212
100213         me.onLabelableRender();
100214
100215         me.callParent(arguments);
100216     },
100217
100218     initRenderTpl: function() {
100219         var me = this;
100220         if (!me.hasOwnProperty('renderTpl')) {
100221             me.renderTpl = me.getTpl('labelableRenderTpl');
100222         }
100223         return me.callParent();
100224     },
100225
100226     initRenderData: function() {
100227         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
100228     },
100229
100230     /**
100231      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
100232      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
100233      * this method to provide a custom generated label.
100234      */
100235     getFieldLabel: function() {
100236         var label = this.fieldLabel || '';
100237         if (!label && this.combineLabels) {
100238             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
100239                 return field.getFieldLabel();
100240             }).join(this.labelConnector);
100241         }
100242         return label;
100243     },
100244
100245     /**
100246      * @private Updates the content of the labelEl if it is rendered
100247      */
100248     updateLabel: function() {
100249         var me = this,
100250             label = me.labelEl;
100251         if (label) {
100252             label.update(me.getFieldLabel());
100253         }
100254     },
100255
100256
100257     /**
100258      * @private Fired when the error message of any field within the container changes, and updates the
100259      * combined error message to match.
100260      */
100261     onFieldErrorChange: function(field, activeError) {
100262         if (this.combineErrors) {
100263             var me = this,
100264                 oldError = me.getActiveError(),
100265                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
100266                     return field.hasActiveError();
100267                 }),
100268                 newErrors = me.getCombinedErrors(invalidFields);
100269
100270             if (newErrors) {
100271                 me.setActiveErrors(newErrors);
100272             } else {
100273                 me.unsetActiveError();
100274             }
100275
100276             if (oldError !== me.getActiveError()) {
100277                 me.doComponentLayout();
100278             }
100279         }
100280     },
100281
100282     /**
100283      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
100284      * messages from them. Defaults to prepending each message by the field name and a colon. This
100285      * can be overridden to provide custom combined error message handling, for instance changing
100286      * the format of each message or sorting the array (it is sorted in order of appearance by default).
100287      * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
100288      * @return {String[]} The combined list of error messages
100289      */
100290     getCombinedErrors: function(invalidFields) {
100291         var forEach = Ext.Array.forEach,
100292             errors = [];
100293         forEach(invalidFields, function(field) {
100294             forEach(field.getActiveErrors(), function(error) {
100295                 var label = field.getFieldLabel();
100296                 errors.push((label ? label + ': ' : '') + error);
100297             });
100298         });
100299         return errors;
100300     },
100301
100302     getTargetEl: function() {
100303         return this.bodyEl || this.callParent();
100304     }
100305 });
100306
100307 /**
100308  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
100309  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
100310  * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
100311  * and {@link #validate validating} the group of checkboxes as a whole.
100312  *
100313  * # Validation
100314  *
100315  * Individual checkbox fields themselves have no default validation behavior, but
100316  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
100317  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
100318  * least one of the checkboxes, the entire group will be highlighted as invalid and the
100319  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
100320  *
100321  * # Layout
100322  *
100323  * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
100324  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
100325  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
100326  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
100327  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
100328  *
100329  *     @example
100330  *     Ext.create('Ext.form.Panel', {
100331  *         title: 'Checkbox Group',
100332  *         width: 300,
100333  *         height: 125,
100334  *         bodyPadding: 10,
100335  *         renderTo: Ext.getBody(),
100336  *         items:[{
100337  *             xtype: 'checkboxgroup',
100338  *             fieldLabel: 'Two Columns',
100339  *             // Arrange radio buttons into two columns, distributed vertically
100340  *             columns: 2,
100341  *             vertical: true,
100342  *             items: [
100343  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
100344  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
100345  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
100346  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
100347  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
100348  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
100349  *             ]
100350  *         }]
100351  *     });
100352  */
100353 Ext.define('Ext.form.CheckboxGroup', {
100354     extend:'Ext.form.FieldContainer',
100355     mixins: {
100356         field: 'Ext.form.field.Field'
100357     },
100358     alias: 'widget.checkboxgroup',
100359     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
100360
100361     /**
100362      * @cfg {String} name
100363      * @hide
100364      */
100365
100366     /**
100367      * @cfg {Ext.form.field.Checkbox[]/Object[]} items
100368      * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
100369      */
100370
100371     /**
100372      * @cfg {String/Number/Number[]} columns
100373      * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
100374      * This config can take several types of values:
100375      *
100376      * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
100377      *   distributed based on the width of the overall field container. This is the default.
100378      * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
100379      *   will be automatically distributed based on the value of {@link #vertical}.
100380      * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
100381      *   width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
100382      *   values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
100383      *   (100%) although if you want the controls to take up the entire field container you should do so.
100384      */
100385     columns : 'auto',
100386
100387     /**
100388      * @cfg {Boolean} vertical
100389      * True to distribute contained controls across columns, completely filling each column top to bottom before
100390      * starting on the next column. The number of controls in each column will be automatically calculated to keep
100391      * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
100392      * completely filling each row left to right before starting on the next row.
100393      */
100394     vertical : false,
100395
100396     /**
100397      * @cfg {Boolean} allowBlank
100398      * False to validate that at least one item in the group is checked. If no items are selected at
100399      * validation time, {@link #blankText} will be used as the error text.
100400      */
100401     allowBlank : true,
100402
100403     /**
100404      * @cfg {String} blankText
100405      * Error text to display if the {@link #allowBlank} validation fails
100406      */
100407     blankText : "You must select at least one item in this group",
100408
100409     // private
100410     defaultType : 'checkboxfield',
100411
100412     // private
100413     groupCls : Ext.baseCSSPrefix + 'form-check-group',
100414
100415     /**
100416      * @cfg {String} fieldBodyCls
100417      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
100418      * Defaults to 'x-form-checkboxgroup-body'.
100419      */
100420     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
100421
100422     // private
100423     layout: 'checkboxgroup',
100424
100425     initComponent: function() {
100426         var me = this;
100427         me.callParent();
100428         me.initField();
100429     },
100430
100431     /**
100432      * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
100433      * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
100434      * after they have been initialized.
100435      * @protected
100436      */
100437     initValue: function() {
100438         var me = this,
100439             valueCfg = me.value;
100440         me.originalValue = me.lastValue = valueCfg || me.getValue();
100441         if (valueCfg) {
100442             me.setValue(valueCfg);
100443         }
100444     },
100445
100446     /**
100447      * When a checkbox is added to the group, monitor it for changes
100448      * @param {Object} field
100449      * @protected
100450      */
100451     onFieldAdded: function(field) {
100452         var me = this;
100453         if (field.isCheckbox) {
100454             me.mon(field, 'change', me.checkChange, me);
100455         }
100456         me.callParent(arguments);
100457     },
100458
100459     onFieldRemoved: function(field) {
100460         var me = this;
100461         if (field.isCheckbox) {
100462             me.mun(field, 'change', me.checkChange, me);
100463         }
100464         me.callParent(arguments);
100465     },
100466
100467     // private override - the group value is a complex object, compare using object serialization
100468     isEqual: function(value1, value2) {
100469         var toQueryString = Ext.Object.toQueryString;
100470         return toQueryString(value1) === toQueryString(value2);
100471     },
100472
100473     /**
100474      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
100475      * is set to true and no items are checked.
100476      * @return {String[]} Array of all validation errors
100477      */
100478     getErrors: function() {
100479         var errors = [];
100480         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
100481             errors.push(this.blankText);
100482         }
100483         return errors;
100484     },
100485
100486     /**
100487      * @private Returns all checkbox components within the container
100488      */
100489     getBoxes: function() {
100490         return this.query('[isCheckbox]');
100491     },
100492
100493     /**
100494      * @private Convenience function which calls the given function for every checkbox in the group
100495      * @param {Function} fn The function to call
100496      * @param {Object} scope (Optional) scope object
100497      */
100498     eachBox: function(fn, scope) {
100499         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
100500     },
100501
100502     /**
100503      * Returns an Array of all checkboxes in the container which are currently checked
100504      * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
100505      */
100506     getChecked: function() {
100507         return Ext.Array.filter(this.getBoxes(), function(cb) {
100508             return cb.getValue();
100509         });
100510     },
100511
100512     // private override
100513     isDirty: function(){
100514         return Ext.Array.some(this.getBoxes(), function(cb) {
100515             return cb.isDirty();
100516         });
100517     },
100518
100519     // private override
100520     setReadOnly: function(readOnly) {
100521         this.eachBox(function(cb) {
100522             cb.setReadOnly(readOnly);
100523         });
100524         this.readOnly = readOnly;
100525     },
100526
100527     /**
100528      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
100529      * loaded values and clears any validation messages.
100530      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
100531      */
100532     reset: function() {
100533         var me = this,
100534             hadError = me.hasActiveError(),
100535             preventMark = me.preventMark;
100536         me.preventMark = true;
100537         me.batchChanges(function() {
100538             me.eachBox(function(cb) {
100539                 cb.reset();
100540             });
100541         });
100542         me.preventMark = preventMark;
100543         me.unsetActiveError();
100544         if (hadError) {
100545             me.doComponentLayout();
100546         }
100547     },
100548
100549     // private override
100550     resetOriginalValue: function() {
100551         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
100552         // the correct data from getValue()
100553         Ext.defer(function() {
100554             this.callParent();
100555         }, 1, this);
100556     },
100557
100558
100559     /**
100560      * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
100561      * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
100562      *
100563      *   - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
100564      *     See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
100565      *   - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
100566      *     of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
100567      *     checked and others will be unchecked.
100568      *
100569      * If a checkbox's name is not in the mapping at all, it will be unchecked.
100570      *
100571      * An example:
100572      *
100573      *     var myCheckboxGroup = new Ext.form.CheckboxGroup({
100574      *         columns: 3,
100575      *         items: [{
100576      *             name: 'cb1',
100577      *             boxLabel: 'Single 1'
100578      *         }, {
100579      *             name: 'cb2',
100580      *             boxLabel: 'Single 2'
100581      *         }, {
100582      *             name: 'cb3',
100583      *             boxLabel: 'Single 3'
100584      *         }, {
100585      *             name: 'cbGroup',
100586      *             boxLabel: 'Grouped 1'
100587      *             inputValue: 'value1'
100588      *         }, {
100589      *             name: 'cbGroup',
100590      *             boxLabel: 'Grouped 2'
100591      *             inputValue: 'value2'
100592      *         }, {
100593      *             name: 'cbGroup',
100594      *             boxLabel: 'Grouped 3'
100595      *             inputValue: 'value3'
100596      *         }]
100597      *     });
100598      *
100599      *     myCheckboxGroup.setValue({
100600      *         cb1: true,
100601      *         cb3: false,
100602      *         cbGroup: ['value1', 'value3']
100603      *     });
100604      *
100605      * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
100606      * 'cbGroup'. The other three checkboxes will be unchecked.
100607      *
100608      * @param {Object} value The mapping of checkbox names to values.
100609      * @return {Ext.form.CheckboxGroup} this
100610      */
100611     setValue: function(value) {
100612         var me = this;
100613         me.batchChanges(function() {
100614             me.eachBox(function(cb) {
100615                 var name = cb.getName(),
100616                     cbValue = false;
100617                 if (value && name in value) {
100618                     if (Ext.isArray(value[name])) {
100619                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
100620                     } else {
100621                         // single value, let the checkbox's own setValue handle conversion
100622                         cbValue = value[name];
100623                     }
100624                 }
100625                 cb.setValue(cbValue);
100626             });
100627         });
100628         return me;
100629     },
100630
100631
100632     /**
100633      * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
100634      * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
100635      * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
100636      * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
100637      * will be an Array of the selected inputValues.
100638      *
100639      * The object format returned from this method can also be passed directly to the {@link #setValue} method.
100640      *
100641      * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
100642      * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
100643      * Ext 4+, use the {@link #getChecked} method instead.
100644      */
100645     getValue: function() {
100646         var values = {};
100647         this.eachBox(function(cb) {
100648             var name = cb.getName(),
100649                 inputValue = cb.inputValue,
100650                 bucket;
100651             if (cb.getValue()) {
100652                 if (name in values) {
100653                     bucket = values[name];
100654                     if (!Ext.isArray(bucket)) {
100655                         bucket = values[name] = [bucket];
100656                     }
100657                     bucket.push(inputValue);
100658                 } else {
100659                     values[name] = inputValue;
100660                 }
100661             }
100662         });
100663         return values;
100664     },
100665
100666     /*
100667      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
100668      */
100669     getSubmitData: function() {
100670         return null;
100671     },
100672
100673     /*
100674      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
100675      */
100676     getModelData: function() {
100677         return null;
100678     },
100679
100680     validate: function() {
100681         var me = this,
100682             errors = me.getErrors(),
100683             isValid = Ext.isEmpty(errors),
100684             wasValid = !me.hasActiveError();
100685
100686         if (isValid) {
100687             me.unsetActiveError();
100688         } else {
100689             me.setActiveError(errors);
100690         }
100691         if (isValid !== wasValid) {
100692             me.fireEvent('validitychange', me, isValid);
100693             me.doComponentLayout();
100694         }
100695
100696         return isValid;
100697     }
100698
100699 }, function() {
100700
100701     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
100702
100703 });
100704
100705
100706 /**
100707  * @private
100708  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
100709  */
100710 Ext.define('Ext.form.CheckboxManager', {
100711     extend: 'Ext.util.MixedCollection',
100712     singleton: true,
100713
100714     getByName: function(name) {
100715         return this.filterBy(function(item) {
100716             return item.name == name;
100717         });
100718     },
100719
100720     getWithValue: function(name, value) {
100721         return this.filterBy(function(item) {
100722             return item.name == name && item.inputValue == value;
100723         });
100724     },
100725
100726     getChecked: function(name) {
100727         return this.filterBy(function(item) {
100728             return item.name == name && item.checked;
100729         });
100730     }
100731 });
100732
100733 /**
100734  * @docauthor Jason Johnston <jason@sencha.com>
100735  *
100736  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
100737  * config will be rendered as the fieldset's `legend`.
100738  *
100739  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
100740  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
100741  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
100742  * layout type.
100743  *
100744  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
100745  *
100746  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
100747  *    the {@link #title legend title}, or:
100748  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
100749  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
100750  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
100751  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
100752  *
100753  * # Example usage
100754  *
100755  *     @example
100756  *     Ext.create('Ext.form.Panel', {
100757  *         title: 'Simple Form with FieldSets',
100758  *         labelWidth: 75, // label settings here cascade unless overridden
100759  *         url: 'save-form.php',
100760  *         frame: true,
100761  *         bodyStyle: 'padding:5px 5px 0',
100762  *         width: 550,
100763  *         renderTo: Ext.getBody(),
100764  *         layout: 'column', // arrange fieldsets side by side
100765  *         defaults: {
100766  *             bodyPadding: 4
100767  *         },
100768  *         items: [{
100769  *             // Fieldset in Column 1 - collapsible via toggle button
100770  *             xtype:'fieldset',
100771  *             columnWidth: 0.5,
100772  *             title: 'Fieldset 1',
100773  *             collapsible: true,
100774  *             defaultType: 'textfield',
100775  *             defaults: {anchor: '100%'},
100776  *             layout: 'anchor',
100777  *             items :[{
100778  *                 fieldLabel: 'Field 1',
100779  *                 name: 'field1'
100780  *             }, {
100781  *                 fieldLabel: 'Field 2',
100782  *                 name: 'field2'
100783  *             }]
100784  *         }, {
100785  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
100786  *             xtype:'fieldset',
100787  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
100788  *             columnWidth: 0.5,
100789  *             checkboxToggle: true,
100790  *             collapsed: true, // fieldset initially collapsed
100791  *             layout:'anchor',
100792  *             items :[{
100793  *                 xtype: 'panel',
100794  *                 anchor: '100%',
100795  *                 title: 'Panel inside a fieldset',
100796  *                 frame: true,
100797  *                 height: 52
100798  *             }]
100799  *         }]
100800  *     });
100801  */
100802 Ext.define('Ext.form.FieldSet', {
100803     extend: 'Ext.container.Container',
100804     alias: 'widget.fieldset',
100805     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
100806
100807     /**
100808      * @cfg {String} title
100809      * A title to be displayed in the fieldset's legend. May contain HTML markup.
100810      */
100811
100812     /**
100813      * @cfg {Boolean} [checkboxToggle=false]
100814      * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
100815      * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
100816      * the {@link #checkboxName}.
100817      */
100818
100819     /**
100820      * @cfg {String} checkboxName
100821      * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
100822      * (defaults to '[fieldset id]-checkbox').
100823      */
100824
100825     /**
100826      * @cfg {Boolean} [collapsible=false]
100827      * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
100828      * into the legend element, false to keep the fieldset statically sized with no collapse button.
100829      * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
100830      * fieldset by default.
100831      */
100832
100833     /**
100834      * @cfg {Boolean} collapsed
100835      * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
100836      * will also be unchecked by default.
100837      */
100838     collapsed: false,
100839
100840     /**
100841      * @property {Ext.Component} legend
100842      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
100843      * created, by setting the {@link #title} or {@link #checkboxToggle} options.
100844      */
100845
100846     /**
100847      * @cfg {String} [baseCls='x-fieldset']
100848      * The base CSS class applied to the fieldset.
100849      */
100850     baseCls: Ext.baseCSSPrefix + 'fieldset',
100851
100852     /**
100853      * @cfg {String} layout
100854      * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
100855      */
100856     layout: 'anchor',
100857
100858     componentLayout: 'fieldset',
100859
100860     // No aria role necessary as fieldset has its own recognized semantics
100861     ariaRole: '',
100862
100863     renderTpl: ['<div id="{id}-body" class="{baseCls}-body"></div>'],
100864
100865     maskOnDisable: false,
100866
100867     getElConfig: function(){
100868         return {tag: 'fieldset', id: this.id};
100869     },
100870
100871     initComponent: function() {
100872         var me = this,
100873             baseCls = me.baseCls;
100874
100875         me.callParent();
100876
100877         // Create the Legend component if needed
100878         me.initLegend();
100879
100880         // Add body el
100881         me.addChildEls('body');
100882
100883         if (me.collapsed) {
100884             me.addCls(baseCls + '-collapsed');
100885             me.collapse();
100886         }
100887     },
100888
100889     // private
100890     onRender: function(container, position) {
100891         this.callParent(arguments);
100892         // Make sure the legend is created and rendered
100893         this.initLegend();
100894     },
100895
100896     /**
100897      * @private
100898      * Initialize and render the legend component if necessary
100899      */
100900     initLegend: function() {
100901         var me = this,
100902             legendItems,
100903             legend = me.legend;
100904
100905         // Create the legend component if needed and it hasn't been already
100906         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
100907             legendItems = [];
100908
100909             // Checkbox
100910             if (me.checkboxToggle) {
100911                 legendItems.push(me.createCheckboxCmp());
100912             }
100913             // Toggle button
100914             else if (me.collapsible) {
100915                 legendItems.push(me.createToggleCmp());
100916             }
100917
100918             // Title
100919             legendItems.push(me.createTitleCmp());
100920
100921             legend = me.legend = Ext.create('Ext.container.Container', {
100922                 baseCls: me.baseCls + '-header',
100923                 ariaRole: '',
100924                 ownerCt: this,
100925                 getElConfig: function(){
100926                     var result = {
100927                         tag: 'legend',
100928                         cls: this.baseCls
100929                     };
100930
100931                     // Gecko3 will kick every <div> out of <legend> and mess up every thing.
100932                     // So here we change every <div> into <span>s. Therefore the following
100933                     // clearer is not needed and since div introduces a lot of subsequent
100934                     // problems, it is actually harmful.
100935                     if (!Ext.isGecko3) {
100936                         result.children = [{
100937                             cls: Ext.baseCSSPrefix + 'clear'
100938                         }];
100939                     }
100940                     return result;
100941                 },
100942                 items: legendItems
100943             });
100944         }
100945
100946         // Make sure legend is rendered if the fieldset is rendered
100947         if (legend && !legend.rendered && me.rendered) {
100948             me.legend.render(me.el, me.body); //insert before body element
100949         }
100950     },
100951
100952     /**
100953      * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
100954      * customize the title component.
100955      * @return Ext.Component
100956      * @protected
100957      */
100958     createTitleCmp: function() {
100959         var me = this;
100960         me.titleCmp = Ext.create('Ext.Component', {
100961             html: me.title,
100962             getElConfig: function() {
100963                 return {
100964                     tag: Ext.isGecko3 ? 'span' : 'div',
100965                     cls: me.titleCmp.cls,
100966                     id: me.titleCmp.id
100967                 };
100968             },
100969             cls: me.baseCls + '-header-text'
100970         });
100971         return me.titleCmp;
100972     },
100973
100974     /**
100975      * @property {Ext.form.field.Checkbox} checkboxCmp
100976      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
100977      * populated if the fieldset is configured with {@link #checkboxToggle}:true.
100978      */
100979
100980     /**
100981      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
100982      * customize the checkbox's configuration or even return an entirely different component type.
100983      * @return Ext.Component
100984      * @protected
100985      */
100986     createCheckboxCmp: function() {
100987         var me = this,
100988             suffix = '-checkbox';
100989
100990         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
100991             getElConfig: function() {
100992                 return {
100993                     tag: Ext.isGecko3 ? 'span' : 'div',
100994                     id: me.checkboxCmp.id,
100995                     cls: me.checkboxCmp.cls
100996                 };
100997             },
100998             name: me.checkboxName || me.id + suffix,
100999             cls: me.baseCls + '-header' + suffix,
101000             checked: !me.collapsed,
101001             listeners: {
101002                 change: me.onCheckChange,
101003                 scope: me
101004             }
101005         });
101006         return me.checkboxCmp;
101007     },
101008
101009     /**
101010      * @property {Ext.panel.Tool} toggleCmp
101011      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
101012      * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
101013      */
101014
101015     /**
101016      * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
101017      * customize the toggle component.
101018      * @return Ext.Component
101019      * @protected
101020      */
101021     createToggleCmp: function() {
101022         var me = this;
101023         me.toggleCmp = Ext.create('Ext.panel.Tool', {
101024             getElConfig: function() {
101025                 return {
101026                     tag: Ext.isGecko3 ? 'span' : 'div',
101027                     id: me.toggleCmp.id,
101028                     cls: me.toggleCmp.cls
101029                 };
101030             },
101031             type: 'toggle',
101032             handler: me.toggle,
101033             scope: me
101034         });
101035         return me.toggleCmp;
101036     },
101037
101038     /**
101039      * Sets the title of this fieldset
101040      * @param {String} title The new title
101041      * @return {Ext.form.FieldSet} this
101042      */
101043     setTitle: function(title) {
101044         var me = this;
101045         me.title = title;
101046         me.initLegend();
101047         me.titleCmp.update(title);
101048         return me;
101049     },
101050
101051     getTargetEl : function() {
101052         return this.body || this.frameBody || this.el;
101053     },
101054
101055     getContentTarget: function() {
101056         return this.body;
101057     },
101058
101059     /**
101060      * @private
101061      * Include the legend component in the items for ComponentQuery
101062      */
101063     getRefItems: function(deep) {
101064         var refItems = this.callParent(arguments),
101065             legend = this.legend;
101066
101067         // Prepend legend items to ensure correct order
101068         if (legend) {
101069             refItems.unshift(legend);
101070             if (deep) {
101071                 refItems.unshift.apply(refItems, legend.getRefItems(true));
101072             }
101073         }
101074         return refItems;
101075     },
101076
101077     /**
101078      * Expands the fieldset.
101079      * @return {Ext.form.FieldSet} this
101080      */
101081     expand : function(){
101082         return this.setExpanded(true);
101083     },
101084
101085     /**
101086      * Collapses the fieldset.
101087      * @return {Ext.form.FieldSet} this
101088      */
101089     collapse : function() {
101090         return this.setExpanded(false);
101091     },
101092
101093     /**
101094      * @private Collapse or expand the fieldset
101095      */
101096     setExpanded: function(expanded) {
101097         var me = this,
101098             checkboxCmp = me.checkboxCmp;
101099
101100         expanded = !!expanded;
101101
101102         if (checkboxCmp) {
101103             checkboxCmp.setValue(expanded);
101104         }
101105
101106         if (expanded) {
101107             me.removeCls(me.baseCls + '-collapsed');
101108         } else {
101109             me.addCls(me.baseCls + '-collapsed');
101110         }
101111         me.collapsed = !expanded;
101112         if (expanded) {
101113             // ensure subitems will get rendered and layed out when expanding
101114             me.getComponentLayout().childrenChanged = true;
101115         }
101116         me.doComponentLayout();
101117         return me;
101118     },
101119
101120     /**
101121      * Toggle the fieldset's collapsed state to the opposite of what it is currently
101122      */
101123     toggle: function() {
101124         this.setExpanded(!!this.collapsed);
101125     },
101126
101127     /**
101128      * @private
101129      * Handle changes in the checkbox checked state
101130      */
101131     onCheckChange: function(cmp, checked) {
101132         this.setExpanded(checked);
101133     },
101134
101135     beforeDestroy : function() {
101136         var legend = this.legend;
101137         if (legend) {
101138             legend.destroy();
101139         }
101140         this.callParent();
101141     }
101142 });
101143
101144 /**
101145  * @docauthor Jason Johnston <jason@sencha.com>
101146  *
101147  * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
101148  * in that form using the {@link #forId} property.
101149  * 
101150  * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
101151  * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
101152  * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
101153  * Ext.form.Label should only be used when your layout can not be achieved with the standard
101154  * {@link Ext.form.Labelable field layout}.
101155  * 
101156  * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
101157  * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
101158  * of that field.
101159  * 
101160  * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
101161  * difference between the two is that the former will automatically escape HTML characters when rendering, while
101162  * the latter will not.
101163  *
101164  * # Example
101165  * 
101166  * This example creates a Label after its associated Text field, an arrangement that cannot currently
101167  * be achieved using the standard Field layout's labelAlign.
101168  * 
101169  *     @example
101170  *     Ext.create('Ext.form.Panel', {
101171  *         title: 'Field with Label',
101172  *         width: 400,
101173  *         bodyPadding: 10,
101174  *         renderTo: Ext.getBody(),
101175  *         layout: {
101176  *             type: 'hbox',
101177  *             align: 'middle'
101178  *         },
101179  *         items: [{
101180  *             xtype: 'textfield',
101181  *             hideLabel: true,
101182  *             flex: 1
101183  *         }, {
101184  *             xtype: 'label',
101185  *             forId: 'myFieldId',
101186  *             text: 'My Awesome Field',
101187  *             margins: '0 0 0 10'
101188  *         }]
101189  *     });
101190  */
101191 Ext.define('Ext.form.Label', {
101192     extend:'Ext.Component',
101193     alias: 'widget.label',
101194     requires: ['Ext.util.Format'],
101195
101196     /**
101197      * @cfg {String} [text='']
101198      * The plain text to display within the label. If you need to include HTML
101199      * tags within the label's innerHTML, use the {@link #html} config instead.
101200      */
101201     /**
101202      * @cfg {String} forId
101203      * The id of the input element to which this label will be bound via the standard HTML 'for'
101204      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
101205      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
101206      * the {@link Ext.form.field.Base#inputId inputId} of that field.
101207      */
101208     /**
101209      * @cfg {String} [html='']
101210      * An HTML fragment that will be used as the label's innerHTML.
101211      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
101212      */
101213     
101214     maskOnDisable: false,
101215     getElConfig: function(){
101216         var me = this;
101217         return {
101218             tag: 'label', 
101219             id: me.id, 
101220             htmlFor: me.forId || '',
101221             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
101222         };
101223     },
101224
101225     /**
101226      * Updates the label's innerHTML with the specified string.
101227      * @param {String} text The new label text
101228      * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
101229      * to the label. This might be useful if you want to include tags in the label's innerHTML rather
101230      * than rendering them as string literals per the default logic.
101231      * @return {Ext.form.Label} this
101232      */
101233     setText : function(text, encode){
101234         var me = this;
101235         
101236         encode = encode !== false;
101237         if(encode) {
101238             me.text = text;
101239             delete me.html;
101240         } else {
101241             me.html = text;
101242             delete me.text;
101243         }
101244         
101245         if(me.rendered){
101246             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
101247         }
101248         return this;
101249     }
101250 });
101251
101252
101253 /**
101254  * @docauthor Jason Johnston <jason@sencha.com>
101255  * 
101256  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
101257  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
101258  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
101259  * working with the BasicForm and the collection of Fields.
101260  * 
101261  * # Layout
101262  * 
101263  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
101264  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
101265  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
101266  * 
101267  * # BasicForm
101268  * 
101269  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
101270  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
101271  * the internal BasicForm when it is created.
101272  * 
101273  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
101274  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
101275  * configuration settings to `this` will *not* affect the BasicForm's configuration.
101276  * 
101277  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
101278  * listened for on the FormPanel itself:
101279  * 
101280  * - {@link Ext.form.Basic#beforeaction beforeaction}
101281  * - {@link Ext.form.Basic#actionfailed actionfailed}
101282  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
101283  * - {@link Ext.form.Basic#validitychange validitychange}
101284  * - {@link Ext.form.Basic#dirtychange dirtychange}
101285  * 
101286  * # Field Defaults
101287  * 
101288  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
101289  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
101290  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
101291  * for details of how the defaults are applied.
101292  * 
101293  * # Form Validation
101294  * 
101295  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
101296  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
101297  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
101298  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
101299  * 
101300  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
101301  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
101302  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
101303  * any component type.
101304  * 
101305  * For more information on form validation see the following:
101306  * 
101307  * - {@link Ext.form.field.Field#validateOnChange}
101308  * - {@link #pollForChanges} and {@link #pollInterval}
101309  * - {@link Ext.form.field.VTypes}
101310  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
101311  * 
101312  * # Form Submission
101313  * 
101314  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
101315  * {@link Ext.form.Basic} for details.
101316  *
101317  * # Example usage
101318  * 
101319  *     @example
101320  *     Ext.create('Ext.form.Panel', {
101321  *         title: 'Simple Form',
101322  *         bodyPadding: 5,
101323  *         width: 350,
101324  * 
101325  *         // The form will submit an AJAX request to this URL when submitted
101326  *         url: 'save-form.php',
101327  * 
101328  *         // Fields will be arranged vertically, stretched to full width
101329  *         layout: 'anchor',
101330  *         defaults: {
101331  *             anchor: '100%'
101332  *         },
101333  * 
101334  *         // The fields
101335  *         defaultType: 'textfield',
101336  *         items: [{
101337  *             fieldLabel: 'First Name',
101338  *             name: 'first',
101339  *             allowBlank: false
101340  *         },{
101341  *             fieldLabel: 'Last Name',
101342  *             name: 'last',
101343  *             allowBlank: false
101344  *         }],
101345  * 
101346  *         // Reset and Submit buttons
101347  *         buttons: [{
101348  *             text: 'Reset',
101349  *             handler: function() {
101350  *                 this.up('form').getForm().reset();
101351  *             }
101352  *         }, {
101353  *             text: 'Submit',
101354  *             formBind: true, //only enabled once the form is valid
101355  *             disabled: true,
101356  *             handler: function() {
101357  *                 var form = this.up('form').getForm();
101358  *                 if (form.isValid()) {
101359  *                     form.submit({
101360  *                         success: function(form, action) {
101361  *                            Ext.Msg.alert('Success', action.result.msg);
101362  *                         },
101363  *                         failure: function(form, action) {
101364  *                             Ext.Msg.alert('Failed', action.result.msg);
101365  *                         }
101366  *                     });
101367  *                 }
101368  *             }
101369  *         }],
101370  *         renderTo: Ext.getBody()
101371  *     });
101372  *
101373  */
101374 Ext.define('Ext.form.Panel', {
101375     extend:'Ext.panel.Panel',
101376     mixins: {
101377         fieldAncestor: 'Ext.form.FieldAncestor'
101378     },
101379     alias: 'widget.form',
101380     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
101381     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
101382
101383     /**
101384      * @cfg {Boolean} pollForChanges
101385      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
101386      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
101387      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
101388      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
101389      * do not fire native events. Defaults to `false`.
101390      */
101391
101392     /**
101393      * @cfg {Number} pollInterval
101394      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
101395      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
101396      */
101397
101398     /**
101399      * @cfg {String} layout
101400      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
101401      * Defaults to `'anchor'`.
101402      */
101403     layout: 'anchor',
101404
101405     ariaRole: 'form',
101406
101407     initComponent: function() {
101408         var me = this;
101409
101410         if (me.frame) {
101411             me.border = false;
101412         }
101413
101414         me.initFieldAncestor();
101415         me.callParent();
101416
101417         me.relayEvents(me.form, [
101418             'beforeaction',
101419             'actionfailed',
101420             'actioncomplete',
101421             'validitychange',
101422             'dirtychange'
101423         ]);
101424
101425         // Start polling if configured
101426         if (me.pollForChanges) {
101427             me.startPolling(me.pollInterval || 500);
101428         }
101429     },
101430
101431     initItems: function() {
101432         // Create the BasicForm
101433         var me = this;
101434
101435         me.form = me.createForm();
101436         me.callParent();
101437         me.form.initialize();
101438     },
101439
101440     /**
101441      * @private
101442      */
101443     createForm: function() {
101444         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
101445     },
101446
101447     /**
101448      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
101449      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
101450      */
101451     getForm: function() {
101452         return this.form;
101453     },
101454
101455     /**
101456      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
101457      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
101458      * @param {Ext.data.Model} record The record to load
101459      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
101460      */
101461     loadRecord: function(record) {
101462         return this.getForm().loadRecord(record);
101463     },
101464
101465     /**
101466      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
101467      * @return {Ext.data.Model} The loaded instance
101468      */
101469     getRecord: function() {
101470         return this.getForm().getRecord();
101471     },
101472
101473     /**
101474      * Convenience function for fetching the current value of each field in the form. This is the same as calling
101475      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
101476      * @return {Object} The current form field values, keyed by field name
101477      */
101478     getValues: function() {
101479         return this.getForm().getValues();
101480     },
101481
101482     beforeDestroy: function() {
101483         this.stopPolling();
101484         this.form.destroy();
101485         this.callParent();
101486     },
101487
101488     /**
101489      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
101490      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
101491      * {@link Ext.form.Basic#doAction} for details)
101492      */
101493     load: function(options) {
101494         this.form.load(options);
101495     },
101496
101497     /**
101498      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
101499      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
101500      * {@link Ext.form.Basic#doAction} for details)
101501      */
101502     submit: function(options) {
101503         this.form.submit(options);
101504     },
101505
101506     /*
101507      * Inherit docs, not using onDisable because it only gets fired
101508      * when the component is rendered.
101509      */
101510     disable: function(silent) {
101511         this.callParent(arguments);
101512         this.form.getFields().each(function(field) {
101513             field.disable();
101514         });
101515     },
101516
101517     /*
101518      * Inherit docs, not using onEnable because it only gets fired
101519      * when the component is rendered.
101520      */
101521     enable: function(silent) {
101522         this.callParent(arguments);
101523         this.form.getFields().each(function(field) {
101524             field.enable();
101525         });
101526     },
101527
101528     /**
101529      * Start an interval task to continuously poll all the fields in the form for changes in their
101530      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
101531      * @param {Number} interval The interval in milliseconds at which the check should run.
101532      */
101533     startPolling: function(interval) {
101534         this.stopPolling();
101535         var task = Ext.create('Ext.util.TaskRunner', interval);
101536         task.start({
101537             interval: 0,
101538             run: this.checkChange,
101539             scope: this
101540         });
101541         this.pollTask = task;
101542     },
101543
101544     /**
101545      * Stop a running interval task that was started by {@link #startPolling}.
101546      */
101547     stopPolling: function() {
101548         var task = this.pollTask;
101549         if (task) {
101550             task.stopAll();
101551             delete this.pollTask;
101552         }
101553     },
101554
101555     /**
101556      * Forces each field within the form panel to
101557      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
101558      */
101559     checkChange: function() {
101560         this.form.getFields().each(function(field) {
101561             field.checkChange();
101562         });
101563     }
101564 });
101565
101566 /**
101567  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
101568  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
101569  * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
101570  * group of radio buttons as a whole.
101571  *
101572  * # Validation
101573  *
101574  * Individual radio buttons themselves have no default validation behavior, but
101575  * sometimes you want to require a user to select one of a group of radios. RadioGroup
101576  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
101577  * one of the radio buttons, the entire group will be highlighted as invalid and the
101578  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
101579  *
101580  * # Layout
101581  *
101582  * The default layout for RadioGroup makes it easy to arrange the radio buttons into
101583  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
101584  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
101585  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
101586  * the Radio components at any depth will still be managed by the RadioGroup's validation.
101587  *
101588  * # Example usage
101589  *
101590  *     @example
101591  *     Ext.create('Ext.form.Panel', {
101592  *         title: 'RadioGroup Example',
101593  *         width: 300,
101594  *         height: 125,
101595  *         bodyPadding: 10,
101596  *         renderTo: Ext.getBody(),
101597  *         items:[{
101598  *             xtype: 'radiogroup',
101599  *             fieldLabel: 'Two Columns',
101600  *             // Arrange radio buttons into two columns, distributed vertically
101601  *             columns: 2,
101602  *             vertical: true,
101603  *             items: [
101604  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
101605  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
101606  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
101607  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
101608  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
101609  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
101610  *             ]
101611  *         }]
101612  *     });
101613  *
101614  */
101615 Ext.define('Ext.form.RadioGroup', {
101616     extend: 'Ext.form.CheckboxGroup',
101617     alias: 'widget.radiogroup',
101618
101619     /**
101620      * @cfg {Ext.form.field.Radio[]/Object[]} items
101621      * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
101622      */
101623     /**
101624      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank.
101625      * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
101626      * be used as the error text.
101627      */
101628     allowBlank : true,
101629     /**
101630      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
101631      */
101632     blankText : 'You must select one item in this group',
101633
101634     // private
101635     defaultType : 'radiofield',
101636
101637     // private
101638     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
101639
101640     getBoxes: function() {
101641         return this.query('[isRadio]');
101642     },
101643
101644     /**
101645      * Sets the value of the radio group. The radio with corresponding name and value will be set.
101646      * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
101647      * for each name.
101648      * 
101649      * @param {Object} value The map from names to values to be set.
101650      * @return {Ext.form.CheckboxGroup} this
101651      */
101652     setValue: function(value) {
101653         var me = this;
101654         if (Ext.isObject(value)) {
101655             Ext.Object.each(value, function(name, cbValue) {
101656                 var radios = Ext.form.RadioManager.getWithValue(name, cbValue);
101657                 radios.each(function(cb) {
101658                     cb.setValue(true);
101659                 });
101660             });
101661         }
101662         return me;
101663     }
101664 });
101665
101666 /**
101667  * @private
101668  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
101669  */
101670 Ext.define('Ext.form.RadioManager', {
101671     extend: 'Ext.util.MixedCollection',
101672     singleton: true,
101673
101674     getByName: function(name) {
101675         return this.filterBy(function(item) {
101676             return item.name == name;
101677         });
101678     },
101679
101680     getWithValue: function(name, value) {
101681         return this.filterBy(function(item) {
101682             return item.name == name && item.inputValue == value;
101683         });
101684     },
101685
101686     getChecked: function(name) {
101687         return this.findBy(function(item) {
101688             return item.name == name && item.checked;
101689         });
101690     }
101691 });
101692
101693 /**
101694  * @class Ext.form.action.DirectLoad
101695  * @extends Ext.form.action.Load
101696  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
101697  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
101698  * <pre><code>
101699 var myFormPanel = new Ext.form.Panel({
101700     // configs for FormPanel
101701     title: 'Basic Information',
101702     renderTo: document.body,
101703     width: 300, height: 160,
101704     padding: 10,
101705
101706     // configs apply to child items
101707     defaults: {anchor: '100%'},
101708     defaultType: 'textfield',
101709     items: [{
101710         fieldLabel: 'Name',
101711         name: 'name'
101712     },{
101713         fieldLabel: 'Email',
101714         name: 'email'
101715     },{
101716         fieldLabel: 'Company',
101717         name: 'company'
101718     }],
101719
101720     // configs for BasicForm
101721     api: {
101722         // The server-side method to call for load() requests
101723         load: Profile.getBasicInfo,
101724         // The server-side must mark the submit handler as a 'formHandler'
101725         submit: Profile.updateBasicInfo
101726     },
101727     // specify the order for the passed params
101728     paramOrder: ['uid', 'foo']
101729 });
101730
101731 // load the form
101732 myFormPanel.getForm().load({
101733     // pass 2 arguments to server side getBasicInfo method (len=2)
101734     params: {
101735         foo: 'bar',
101736         uid: 34
101737     }
101738 });
101739  * </code></pre>
101740  * The data packet sent to the server will resemble something like:
101741  * <pre><code>
101742 [
101743     {
101744         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101745         "data":[34,"bar"] // note the order of the params
101746     }
101747 ]
101748  * </code></pre>
101749  * The form will process a data packet returned by the server that is similar
101750  * to the following format:
101751  * <pre><code>
101752 [
101753     {
101754         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101755         "result":{
101756             "success":true,
101757             "data":{
101758                 "name":"Fred Flintstone",
101759                 "company":"Slate Rock and Gravel",
101760                 "email":"fred.flintstone@slaterg.com"
101761             }
101762         }
101763     }
101764 ]
101765  * </code></pre>
101766  */
101767 Ext.define('Ext.form.action.DirectLoad', {
101768     extend:'Ext.form.action.Load',
101769     requires: ['Ext.direct.Manager'],
101770     alternateClassName: 'Ext.form.Action.DirectLoad',
101771     alias: 'formaction.directload',
101772
101773     type: 'directload',
101774
101775     run: function() {
101776         this.form.api.load.apply(window, this.getArgs());
101777     },
101778
101779     /**
101780      * @private
101781      * Build the arguments to be sent to the Direct call.
101782      * @return Array
101783      */
101784     getArgs: function() {
101785         var me = this,
101786             args = [],
101787             form = me.form,
101788             paramOrder = form.paramOrder,
101789             params = me.getParams(),
101790             i, len;
101791
101792         // If a paramOrder was specified, add the params into the argument list in that order.
101793         if (paramOrder) {
101794             for (i = 0, len = paramOrder.length; i < len; i++) {
101795                 args.push(params[paramOrder[i]]);
101796             }
101797         }
101798         // If paramsAsHash was specified, add all the params as a single object argument.
101799         else if (form.paramsAsHash) {
101800             args.push(params);
101801         }
101802
101803         // Add the callback and scope to the end of the arguments list
101804         args.push(me.onSuccess, me);
101805
101806         return args;
101807     },
101808
101809     // Direct actions have already been processed and therefore
101810     // we can directly set the result; Direct Actions do not have
101811     // a this.response property.
101812     processResponse: function(result) {
101813         return (this.result = result);
101814     },
101815
101816     onSuccess: function(result, trans) {
101817         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
101818             result = {};
101819         }
101820         this.callParent([result]);
101821     }
101822 });
101823
101824
101825
101826 /**
101827  * @class Ext.form.action.DirectSubmit
101828  * @extends Ext.form.action.Submit
101829  * <p>Provides Ext.direct support for submitting form data.</p>
101830  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
101831  * <pre><code>
101832 var myFormPanel = new Ext.form.Panel({
101833     // configs for FormPanel
101834     title: 'Basic Information',
101835     renderTo: document.body,
101836     width: 300, height: 160,
101837     padding: 10,
101838     buttons:[{
101839         text: 'Submit',
101840         handler: function(){
101841             myFormPanel.getForm().submit({
101842                 params: {
101843                     foo: 'bar',
101844                     uid: 34
101845                 }
101846             });
101847         }
101848     }],
101849
101850     // configs apply to child items
101851     defaults: {anchor: '100%'},
101852     defaultType: 'textfield',
101853     items: [{
101854         fieldLabel: 'Name',
101855         name: 'name'
101856     },{
101857         fieldLabel: 'Email',
101858         name: 'email'
101859     },{
101860         fieldLabel: 'Company',
101861         name: 'company'
101862     }],
101863
101864     // configs for BasicForm
101865     api: {
101866         // The server-side method to call for load() requests
101867         load: Profile.getBasicInfo,
101868         // The server-side must mark the submit handler as a 'formHandler'
101869         submit: Profile.updateBasicInfo
101870     },
101871     // specify the order for the passed params
101872     paramOrder: ['uid', 'foo']
101873 });
101874  * </code></pre>
101875  * The data packet sent to the server will resemble something like:
101876  * <pre><code>
101877 {
101878     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101879     "result":{
101880         "success":true,
101881         "id":{
101882             "extAction":"Profile","extMethod":"updateBasicInfo",
101883             "extType":"rpc","extTID":"6","extUpload":"false",
101884             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
101885         }
101886     }
101887 }
101888  * </code></pre>
101889  * The form will process a data packet returned by the server that is similar
101890  * to the following:
101891  * <pre><code>
101892 // sample success packet (batched requests)
101893 [
101894     {
101895         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
101896         "result":{
101897             "success":true
101898         }
101899     }
101900 ]
101901
101902 // sample failure packet (one request)
101903 {
101904         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101905         "result":{
101906             "errors":{
101907                 "email":"already taken"
101908             },
101909             "success":false,
101910             "foo":"bar"
101911         }
101912 }
101913  * </code></pre>
101914  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
101915  */
101916 Ext.define('Ext.form.action.DirectSubmit', {
101917     extend:'Ext.form.action.Submit',
101918     requires: ['Ext.direct.Manager'],
101919     alternateClassName: 'Ext.form.Action.DirectSubmit',
101920     alias: 'formaction.directsubmit',
101921
101922     type: 'directsubmit',
101923
101924     doSubmit: function() {
101925         var me = this,
101926             callback = Ext.Function.bind(me.onSuccess, me),
101927             formEl = me.buildForm();
101928         me.form.api.submit(formEl, callback, me);
101929         Ext.removeNode(formEl);
101930     },
101931
101932     // Direct actions have already been processed and therefore
101933     // we can directly set the result; Direct Actions do not have
101934     // a this.response property.
101935     processResponse: function(result) {
101936         return (this.result = result);
101937     },
101938
101939     onSuccess: function(response, trans) {
101940         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
101941             response = {};
101942         }
101943         this.callParent([response]);
101944     }
101945 });
101946
101947 /**
101948  * @class Ext.form.action.StandardSubmit
101949  * @extends Ext.form.action.Submit
101950  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
101951  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
101952  * <p>If validation of the form fields fails, the Form's afterAction method
101953  * will be called. Otherwise, afterAction will not be called.</p>
101954  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
101955  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
101956  * config option is <tt>true</tt>.</p>
101957  */
101958 Ext.define('Ext.form.action.StandardSubmit', {
101959     extend:'Ext.form.action.Submit',
101960     alias: 'formaction.standardsubmit',
101961
101962     /**
101963      * @cfg {String} target
101964      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
101965      * the target will be the current window/frame.
101966      */
101967
101968     /**
101969      * @private
101970      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
101971      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
101972      * {@link Ext.form.Basic#baseParams baseParams}.
101973      */
101974     doSubmit: function() {
101975         var form = this.buildForm();
101976         form.submit();
101977         Ext.removeNode(form);
101978     }
101979
101980 });
101981
101982 /**
101983  * @docauthor Robert Dougan <rob@sencha.com>
101984  *
101985  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
101986  * parent class for {@link Ext.form.field.Radio radio buttons}.
101987  *
101988  * # Labeling
101989  *
101990  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
101991  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
101992  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
101993  *
101994  * # Values
101995  *
101996  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
101997  * The following values will check the checkbox:
101998  *
101999  * - `true`
102000  * - `'true'`
102001  * - `'1'`
102002  * - `'on'`
102003  *
102004  * Any other value will uncheck the checkbox.
102005  *
102006  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
102007  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
102008  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
102009  * will be used.
102010  *
102011  * # Example usage
102012  *
102013  *     @example
102014  *     Ext.create('Ext.form.Panel', {
102015  *         bodyPadding: 10,
102016  *         width: 300,
102017  *         title: 'Pizza Order',
102018  *         items: [
102019  *             {
102020  *                 xtype: 'fieldcontainer',
102021  *                 fieldLabel: 'Toppings',
102022  *                 defaultType: 'checkboxfield',
102023  *                 items: [
102024  *                     {
102025  *                         boxLabel  : 'Anchovies',
102026  *                         name      : 'topping',
102027  *                         inputValue: '1',
102028  *                         id        : 'checkbox1'
102029  *                     }, {
102030  *                         boxLabel  : 'Artichoke Hearts',
102031  *                         name      : 'topping',
102032  *                         inputValue: '2',
102033  *                         checked   : true,
102034  *                         id        : 'checkbox2'
102035  *                     }, {
102036  *                         boxLabel  : 'Bacon',
102037  *                         name      : 'topping',
102038  *                         inputValue: '3',
102039  *                         id        : 'checkbox3'
102040  *                     }
102041  *                 ]
102042  *             }
102043  *         ],
102044  *         bbar: [
102045  *             {
102046  *                 text: 'Select Bacon',
102047  *                 handler: function() {
102048  *                     Ext.getCmp('checkbox3').setValue(true);
102049  *                 }
102050  *             },
102051  *             '-',
102052  *             {
102053  *                 text: 'Select All',
102054  *                 handler: function() {
102055  *                     Ext.getCmp('checkbox1').setValue(true);
102056  *                     Ext.getCmp('checkbox2').setValue(true);
102057  *                     Ext.getCmp('checkbox3').setValue(true);
102058  *                 }
102059  *             },
102060  *             {
102061  *                 text: 'Deselect All',
102062  *                 handler: function() {
102063  *                     Ext.getCmp('checkbox1').setValue(false);
102064  *                     Ext.getCmp('checkbox2').setValue(false);
102065  *                     Ext.getCmp('checkbox3').setValue(false);
102066  *                 }
102067  *             }
102068  *         ],
102069  *         renderTo: Ext.getBody()
102070  *     });
102071  */
102072 Ext.define('Ext.form.field.Checkbox', {
102073     extend: 'Ext.form.field.Base',
102074     alias: ['widget.checkboxfield', 'widget.checkbox'],
102075     alternateClassName: 'Ext.form.Checkbox',
102076     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
102077
102078     // note: {id} here is really {inputId}, but {cmpId} is available
102079     fieldSubTpl: [
102080         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
102081             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
102082         '</tpl>',
102083         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
102084         // styled with a custom checkbox image. This allows greater control and consistency in
102085         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
102086         '<input type="button" id="{id}" ',
102087             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
102088             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
102089         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
102090             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
102091         '</tpl>',
102092         {
102093             disableFormats: true,
102094             compiled: true
102095         }
102096     ],
102097
102098     isCheckbox: true,
102099
102100     /**
102101      * @cfg {String} [focusCls='x-form-cb-focus']
102102      * The CSS class to use when the checkbox receives focus
102103      */
102104     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
102105
102106     /**
102107      * @cfg {String} [fieldCls='x-form-field']
102108      * The default CSS class for the checkbox
102109      */
102110
102111     /**
102112      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
102113      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
102114      * .
102115      */
102116     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
102117
102118     /**
102119      * @cfg {Boolean} checked
102120      * true if the checkbox should render initially checked
102121      */
102122     checked: false,
102123
102124     /**
102125      * @cfg {String} [checkedCls='x-form-cb-checked']
102126      * The CSS class added to the component's main element when it is in the checked state.
102127      */
102128     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
102129
102130     /**
102131      * @cfg {String} boxLabel
102132      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
102133      * determined by the {@link #boxLabelAlign} config.
102134      */
102135
102136     /**
102137      * @cfg {String} [boxLabelCls='x-form-cb-label']
102138      * The CSS class to be applied to the {@link #boxLabel} element
102139      */
102140     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
102141
102142     /**
102143      * @cfg {String} boxLabelAlign
102144      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
102145      * and 'after'.
102146      */
102147     boxLabelAlign: 'after',
102148
102149     /**
102150      * @cfg {String} inputValue
102151      * The value that should go into the generated input element's value attribute and should be used as the parameter
102152      * value when submitting as part of a form.
102153      */
102154     inputValue: 'on',
102155
102156     /**
102157      * @cfg {String} uncheckedValue
102158      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
102159      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
102160      * submitted (the default behavior of HTML checkboxes).
102161      */
102162
102163     /**
102164      * @cfg {Function} handler
102165      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
102166      * change event}).
102167      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
102168      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
102169      */
102170
102171     /**
102172      * @cfg {Object} scope
102173      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
102174      */
102175
102176     // private overrides
102177     checkChangeEvents: [],
102178     inputType: 'checkbox',
102179     ariaRole: 'checkbox',
102180
102181     // private
102182     onRe: /^on$/i,
102183
102184     initComponent: function(){
102185         this.callParent(arguments);
102186         this.getManager().add(this);
102187     },
102188
102189     initValue: function() {
102190         var me = this,
102191             checked = !!me.checked;
102192
102193         /**
102194          * @property {Object} originalValue
102195          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
102196          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
102197          */
102198         me.originalValue = me.lastValue = checked;
102199
102200         // Set the initial checked state
102201         me.setValue(checked);
102202     },
102203
102204     // private
102205     onRender : function(ct, position) {
102206         var me = this;
102207
102208         /**
102209          * @property {Ext.Element} boxLabelEl
102210          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
102211          * rendered and has a boxLabel configured.
102212          */
102213         me.addChildEls('boxLabelEl');
102214
102215         Ext.applyIf(me.subTplData, {
102216             boxLabel: me.boxLabel,
102217             boxLabelCls: me.boxLabelCls,
102218             boxLabelAlign: me.boxLabelAlign
102219         });
102220
102221         me.callParent(arguments);
102222     },
102223
102224     initEvents: function() {
102225         var me = this;
102226         me.callParent();
102227         me.mon(me.inputEl, 'click', me.onBoxClick, me);
102228     },
102229
102230     /**
102231      * @private Handle click on the checkbox button
102232      */
102233     onBoxClick: function(e) {
102234         var me = this;
102235         if (!me.disabled && !me.readOnly) {
102236             this.setValue(!this.checked);
102237         }
102238     },
102239
102240     /**
102241      * Returns the checked state of the checkbox.
102242      * @return {Boolean} True if checked, else false
102243      */
102244     getRawValue: function() {
102245         return this.checked;
102246     },
102247
102248     /**
102249      * Returns the checked state of the checkbox.
102250      * @return {Boolean} True if checked, else false
102251      */
102252     getValue: function() {
102253         return this.checked;
102254     },
102255
102256     /**
102257      * Returns the submit value for the checkbox which can be used when submitting forms.
102258      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
102259      */
102260     getSubmitValue: function() {
102261         var unchecked = this.uncheckedValue,
102262             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
102263         return this.checked ? this.inputValue : uncheckedVal;
102264     },
102265
102266     /**
102267      * Sets the checked state of the checkbox.
102268      *
102269      * @param {Boolean/String/Number} value The following values will check the checkbox:
102270      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
102271      * Any other value will uncheck the checkbox.
102272      * @return {Boolean} the new checked state of the checkbox
102273      */
102274     setRawValue: function(value) {
102275         var me = this,
102276             inputEl = me.inputEl,
102277             inputValue = me.inputValue,
102278             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
102279                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
102280
102281         if (inputEl) {
102282             inputEl.dom.setAttribute('aria-checked', checked);
102283             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
102284         }
102285
102286         me.checked = me.rawValue = checked;
102287         return checked;
102288     },
102289
102290     /**
102291      * Sets the checked state of the checkbox, and invokes change detection.
102292      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
102293      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
102294      * @return {Ext.form.field.Checkbox} this
102295      */
102296     setValue: function(checked) {
102297         var me = this;
102298
102299         // If an array of strings is passed, find all checkboxes in the group with the same name as this
102300         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
102301         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
102302         // don't want users depending on this behavior.
102303         if (Ext.isArray(checked)) {
102304             me.getManager().getByName(me.name).each(function(cb) {
102305                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
102306             });
102307         } else {
102308             me.callParent(arguments);
102309         }
102310
102311         return me;
102312     },
102313
102314     // private
102315     valueToRaw: function(value) {
102316         // No extra conversion for checkboxes
102317         return value;
102318     },
102319
102320     /**
102321      * @private
102322      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
102323      * function if specified.
102324      */
102325     onChange: function(newVal, oldVal) {
102326         var me = this,
102327             handler = me.handler;
102328         if (handler) {
102329             handler.call(me.scope || me, me, newVal);
102330         }
102331         me.callParent(arguments);
102332     },
102333
102334     // inherit docs
102335     beforeDestroy: function(){
102336         this.callParent();
102337         this.getManager().removeAtKey(this.id);
102338     },
102339
102340     // inherit docs
102341     getManager: function() {
102342         return Ext.form.CheckboxManager;
102343     },
102344
102345     onEnable: function() {
102346         var me = this,
102347             inputEl = me.inputEl;
102348         me.callParent();
102349         if (inputEl) {
102350             // Can still be disabled if the field is readOnly
102351             inputEl.dom.disabled = me.readOnly;
102352         }
102353     },
102354
102355     setReadOnly: function(readOnly) {
102356         var me = this,
102357             inputEl = me.inputEl;
102358         if (inputEl) {
102359             // Set the button to disabled when readonly
102360             inputEl.dom.disabled = readOnly || me.disabled;
102361         }
102362         me.readOnly = readOnly;
102363     },
102364
102365     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
102366     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
102367     getBodyNaturalWidth: function() {
102368         var me = this,
102369             bodyEl = me.bodyEl,
102370             ws = 'white-space',
102371             width;
102372         bodyEl.setStyle(ws, 'nowrap');
102373         width = bodyEl.getWidth();
102374         bodyEl.setStyle(ws, '');
102375         return width;
102376     }
102377
102378 });
102379
102380 /**
102381  * @private
102382  * @class Ext.layout.component.field.Trigger
102383  * @extends Ext.layout.component.field.Field
102384  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
102385  * the trigger button(s).
102386  * @private
102387  */
102388
102389 Ext.define('Ext.layout.component.field.Trigger', {
102390
102391     /* Begin Definitions */
102392
102393     alias: ['layout.triggerfield'],
102394
102395     extend: 'Ext.layout.component.field.Field',
102396
102397     /* End Definitions */
102398
102399     type: 'triggerfield',
102400
102401     sizeBodyContents: function(width, height) {
102402         var me = this,
102403             owner = me.owner,
102404             inputEl = owner.inputEl,
102405             triggerWrap = owner.triggerWrap,
102406             triggerWidth = owner.getTriggerWidth();
102407
102408         // If we or our ancestor is hidden, we can get a triggerWidth calculation
102409         // of 0.  We don't want to resize in this case.
102410         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
102411             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
102412             // are floated left in CSS so they'll stack up side by side.
102413             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
102414     
102415             // Explicitly set the triggerWrap's width, to prevent wrapping
102416             triggerWrap.setWidth(triggerWidth);
102417         }
102418     }
102419 });
102420 /**
102421  * A mechanism for displaying data using custom layout templates and formatting.
102422  *
102423  * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
102424  * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
102425  * to reflect the changes. The view also provides built-in behavior for many common events that can
102426  * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
102427  * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
102428  * be provided for the DataView to determine what nodes it will be working with.**
102429  *
102430  * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
102431  *
102432  *     @example
102433  *     Ext.define('Image', {
102434  *         extend: 'Ext.data.Model',
102435  *         fields: [
102436  *             { name:'src', type:'string' },
102437  *             { name:'caption', type:'string' }
102438  *         ]
102439  *     });
102440  *
102441  *     Ext.create('Ext.data.Store', {
102442  *         id:'imagesStore',
102443  *         model: 'Image',
102444  *         data: [
102445  *             { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
102446  *             { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
102447  *             { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
102448  *             { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
102449  *         ]
102450  *     });
102451  *
102452  *     var imageTpl = new Ext.XTemplate(
102453  *         '<tpl for=".">',
102454  *             '<div style="margin-bottom: 10px;" class="thumb-wrap">',
102455  *               '<img src="{src}" />',
102456  *               '<br/><span>{caption}</span>',
102457  *             '</div>',
102458  *         '</tpl>'
102459  *     );
102460  *
102461  *     Ext.create('Ext.view.View', {
102462  *         store: Ext.data.StoreManager.lookup('imagesStore'),
102463  *         tpl: imageTpl,
102464  *         itemSelector: 'div.thumb-wrap',
102465  *         emptyText: 'No images available',
102466  *         renderTo: Ext.getBody()
102467  *     });
102468  */
102469 Ext.define('Ext.view.View', {
102470     extend: 'Ext.view.AbstractView',
102471     alternateClassName: 'Ext.DataView',
102472     alias: 'widget.dataview',
102473
102474     inheritableStatics: {
102475         EventMap: {
102476             mousedown: 'MouseDown',
102477             mouseup: 'MouseUp',
102478             click: 'Click',
102479             dblclick: 'DblClick',
102480             contextmenu: 'ContextMenu',
102481             mouseover: 'MouseOver',
102482             mouseout: 'MouseOut',
102483             mouseenter: 'MouseEnter',
102484             mouseleave: 'MouseLeave',
102485             keydown: 'KeyDown',
102486             focus: 'Focus'
102487         }
102488     },
102489
102490     addCmpEvents: function() {
102491         this.addEvents(
102492             /**
102493              * @event beforeitemmousedown
102494              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
102495              * @param {Ext.view.View} this
102496              * @param {Ext.data.Model} record The record that belongs to the item
102497              * @param {HTMLElement} item The item's element
102498              * @param {Number} index The item's index
102499              * @param {Ext.EventObject} e The raw event object
102500              */
102501             'beforeitemmousedown',
102502             /**
102503              * @event beforeitemmouseup
102504              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
102505              * @param {Ext.view.View} this
102506              * @param {Ext.data.Model} record The record that belongs to the item
102507              * @param {HTMLElement} item The item's element
102508              * @param {Number} index The item's index
102509              * @param {Ext.EventObject} e The raw event object
102510              */
102511             'beforeitemmouseup',
102512             /**
102513              * @event beforeitemmouseenter
102514              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
102515              * @param {Ext.view.View} this
102516              * @param {Ext.data.Model} record The record that belongs to the item
102517              * @param {HTMLElement} item The item's element
102518              * @param {Number} index The item's index
102519              * @param {Ext.EventObject} e The raw event object
102520              */
102521             'beforeitemmouseenter',
102522             /**
102523              * @event beforeitemmouseleave
102524              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
102525              * @param {Ext.view.View} this
102526              * @param {Ext.data.Model} record The record that belongs to the item
102527              * @param {HTMLElement} item The item's element
102528              * @param {Number} index The item's index
102529              * @param {Ext.EventObject} e The raw event object
102530              */
102531             'beforeitemmouseleave',
102532             /**
102533              * @event beforeitemclick
102534              * Fires before the click event on an item is processed. Returns false to cancel the default action.
102535              * @param {Ext.view.View} this
102536              * @param {Ext.data.Model} record The record that belongs to the item
102537              * @param {HTMLElement} item The item's element
102538              * @param {Number} index The item's index
102539              * @param {Ext.EventObject} e The raw event object
102540              */
102541             'beforeitemclick',
102542             /**
102543              * @event beforeitemdblclick
102544              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
102545              * @param {Ext.view.View} this
102546              * @param {Ext.data.Model} record The record that belongs to the item
102547              * @param {HTMLElement} item The item's element
102548              * @param {Number} index The item's index
102549              * @param {Ext.EventObject} e The raw event object
102550              */
102551             'beforeitemdblclick',
102552             /**
102553              * @event beforeitemcontextmenu
102554              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
102555              * @param {Ext.view.View} this
102556              * @param {Ext.data.Model} record The record that belongs to the item
102557              * @param {HTMLElement} item The item's element
102558              * @param {Number} index The item's index
102559              * @param {Ext.EventObject} e The raw event object
102560              */
102561             'beforeitemcontextmenu',
102562             /**
102563              * @event beforeitemkeydown
102564              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
102565              * @param {Ext.view.View} this
102566              * @param {Ext.data.Model} record The record that belongs to the item
102567              * @param {HTMLElement} item The item's element
102568              * @param {Number} index The item's index
102569              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102570              */
102571             'beforeitemkeydown',
102572             /**
102573              * @event itemmousedown
102574              * Fires when there is a mouse down on an item
102575              * @param {Ext.view.View} this
102576              * @param {Ext.data.Model} record The record that belongs to the item
102577              * @param {HTMLElement} item The item's element
102578              * @param {Number} index The item's index
102579              * @param {Ext.EventObject} e The raw event object
102580              */
102581             'itemmousedown',
102582             /**
102583              * @event itemmouseup
102584              * Fires when there is a mouse up on an item
102585              * @param {Ext.view.View} this
102586              * @param {Ext.data.Model} record The record that belongs to the item
102587              * @param {HTMLElement} item The item's element
102588              * @param {Number} index The item's index
102589              * @param {Ext.EventObject} e The raw event object
102590              */
102591             'itemmouseup',
102592             /**
102593              * @event itemmouseenter
102594              * Fires when the mouse enters an item.
102595              * @param {Ext.view.View} this
102596              * @param {Ext.data.Model} record The record that belongs to the item
102597              * @param {HTMLElement} item The item's element
102598              * @param {Number} index The item's index
102599              * @param {Ext.EventObject} e The raw event object
102600              */
102601             'itemmouseenter',
102602             /**
102603              * @event itemmouseleave
102604              * Fires when the mouse leaves an item.
102605              * @param {Ext.view.View} this
102606              * @param {Ext.data.Model} record The record that belongs to the item
102607              * @param {HTMLElement} item The item's element
102608              * @param {Number} index The item's index
102609              * @param {Ext.EventObject} e The raw event object
102610              */
102611             'itemmouseleave',
102612             /**
102613              * @event itemclick
102614              * Fires when an item is clicked.
102615              * @param {Ext.view.View} this
102616              * @param {Ext.data.Model} record The record that belongs to the item
102617              * @param {HTMLElement} item The item's element
102618              * @param {Number} index The item's index
102619              * @param {Ext.EventObject} e The raw event object
102620              */
102621             'itemclick',
102622             /**
102623              * @event itemdblclick
102624              * Fires when an item is double clicked.
102625              * @param {Ext.view.View} this
102626              * @param {Ext.data.Model} record The record that belongs to the item
102627              * @param {HTMLElement} item The item's element
102628              * @param {Number} index The item's index
102629              * @param {Ext.EventObject} e The raw event object
102630              */
102631             'itemdblclick',
102632             /**
102633              * @event itemcontextmenu
102634              * Fires when an item is right clicked.
102635              * @param {Ext.view.View} this
102636              * @param {Ext.data.Model} record The record that belongs to the item
102637              * @param {HTMLElement} item The item's element
102638              * @param {Number} index The item's index
102639              * @param {Ext.EventObject} e The raw event object
102640              */
102641             'itemcontextmenu',
102642             /**
102643              * @event itemkeydown
102644              * Fires when a key is pressed while an item is currently selected.
102645              * @param {Ext.view.View} this
102646              * @param {Ext.data.Model} record The record that belongs to the item
102647              * @param {HTMLElement} item The item's element
102648              * @param {Number} index The item's index
102649              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102650              */
102651             'itemkeydown',
102652             /**
102653              * @event beforecontainermousedown
102654              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
102655              * @param {Ext.view.View} this
102656              * @param {Ext.EventObject} e The raw event object
102657              */
102658             'beforecontainermousedown',
102659             /**
102660              * @event beforecontainermouseup
102661              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
102662              * @param {Ext.view.View} this
102663              * @param {Ext.EventObject} e The raw event object
102664              */
102665             'beforecontainermouseup',
102666             /**
102667              * @event beforecontainermouseover
102668              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
102669              * @param {Ext.view.View} this
102670              * @param {Ext.EventObject} e The raw event object
102671              */
102672             'beforecontainermouseover',
102673             /**
102674              * @event beforecontainermouseout
102675              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
102676              * @param {Ext.view.View} this
102677              * @param {Ext.EventObject} e The raw event object
102678              */
102679             'beforecontainermouseout',
102680             /**
102681              * @event beforecontainerclick
102682              * Fires before the click event on the container is processed. Returns false to cancel the default action.
102683              * @param {Ext.view.View} this
102684              * @param {Ext.EventObject} e The raw event object
102685              */
102686             'beforecontainerclick',
102687             /**
102688              * @event beforecontainerdblclick
102689              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
102690              * @param {Ext.view.View} this
102691              * @param {Ext.EventObject} e The raw event object
102692              */
102693             'beforecontainerdblclick',
102694             /**
102695              * @event beforecontainercontextmenu
102696              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
102697              * @param {Ext.view.View} this
102698              * @param {Ext.EventObject} e The raw event object
102699              */
102700             'beforecontainercontextmenu',
102701             /**
102702              * @event beforecontainerkeydown
102703              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
102704              * @param {Ext.view.View} this
102705              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102706              */
102707             'beforecontainerkeydown',
102708             /**
102709              * @event containermouseup
102710              * Fires when there is a mouse up on the container
102711              * @param {Ext.view.View} this
102712              * @param {Ext.EventObject} e The raw event object
102713              */
102714             'containermouseup',
102715             /**
102716              * @event containermouseover
102717              * Fires when you move the mouse over the container.
102718              * @param {Ext.view.View} this
102719              * @param {Ext.EventObject} e The raw event object
102720              */
102721             'containermouseover',
102722             /**
102723              * @event containermouseout
102724              * Fires when you move the mouse out of the container.
102725              * @param {Ext.view.View} this
102726              * @param {Ext.EventObject} e The raw event object
102727              */
102728             'containermouseout',
102729             /**
102730              * @event containerclick
102731              * Fires when the container is clicked.
102732              * @param {Ext.view.View} this
102733              * @param {Ext.EventObject} e The raw event object
102734              */
102735             'containerclick',
102736             /**
102737              * @event containerdblclick
102738              * Fires when the container is double clicked.
102739              * @param {Ext.view.View} this
102740              * @param {Ext.EventObject} e The raw event object
102741              */
102742             'containerdblclick',
102743             /**
102744              * @event containercontextmenu
102745              * Fires when the container is right clicked.
102746              * @param {Ext.view.View} this
102747              * @param {Ext.EventObject} e The raw event object
102748              */
102749             'containercontextmenu',
102750             /**
102751              * @event containerkeydown
102752              * Fires when a key is pressed while the container is focused, and no item is currently selected.
102753              * @param {Ext.view.View} this
102754              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102755              */
102756             'containerkeydown',
102757
102758             /**
102759              * @event selectionchange
102760              * Fires when the selected nodes change. Relayed event from the underlying selection model.
102761              * @param {Ext.view.View} this
102762              * @param {HTMLElement[]} selections Array of the selected nodes
102763              */
102764             'selectionchange',
102765             /**
102766              * @event beforeselect
102767              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
102768              * @param {Ext.view.View} this
102769              * @param {HTMLElement} node The node to be selected
102770              * @param {HTMLElement[]} selections Array of currently selected nodes
102771              */
102772             'beforeselect'
102773         );
102774     },
102775     // private
102776     afterRender: function(){
102777         var me = this,
102778             listeners;
102779
102780         me.callParent();
102781
102782         listeners = {
102783             scope: me,
102784             /*
102785              * We need to make copies of this since some of the events fired here will end up triggering
102786              * a new event to be called and the shared event object will be mutated. In future we should
102787              * investigate if there are any issues with creating a new event object for each event that
102788              * is fired.
102789              */
102790             freezeEvent: true,
102791             click: me.handleEvent,
102792             mousedown: me.handleEvent,
102793             mouseup: me.handleEvent,
102794             dblclick: me.handleEvent,
102795             contextmenu: me.handleEvent,
102796             mouseover: me.handleEvent,
102797             mouseout: me.handleEvent,
102798             keydown: me.handleEvent
102799         };
102800
102801         me.mon(me.getTargetEl(), listeners);
102802
102803         if (me.store) {
102804             me.bindStore(me.store, true);
102805         }
102806     },
102807
102808     handleEvent: function(e) {
102809         if (this.processUIEvent(e) !== false) {
102810             this.processSpecialEvent(e);
102811         }
102812     },
102813
102814     // Private template method
102815     processItemEvent: Ext.emptyFn,
102816     processContainerEvent: Ext.emptyFn,
102817     processSpecialEvent: Ext.emptyFn,
102818
102819     /*
102820      * Returns true if this mouseover/out event is still over the overItem.
102821      */
102822     stillOverItem: function (event, overItem) {
102823         var nowOver;
102824
102825         // There is this weird bug when you hover over the border of a cell it is saying
102826         // the target is the table.
102827         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
102828         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
102829         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
102830         // this hard exception.
102831         if (overItem && typeof(overItem.offsetParent) === "object") {
102832             // mouseout : relatedTarget == nowOver, target == wasOver
102833             // mouseover: relatedTarget == wasOver, target == nowOver
102834             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
102835             return Ext.fly(overItem).contains(nowOver);
102836         }
102837
102838         return false;
102839     },
102840
102841     processUIEvent: function(e) {
102842         var me = this,
102843             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
102844             map = this.statics().EventMap,
102845             index, record,
102846             type = e.type,
102847             overItem = me.mouseOverItem,
102848             newType;
102849
102850         if (!item) {
102851             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
102852                 item = overItem;
102853             }
102854
102855             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
102856             if (type == 'keydown') {
102857                 record = me.getSelectionModel().getLastSelected();
102858                 if (record) {
102859                     item = me.getNode(record);
102860                 }
102861             }
102862         }
102863
102864         if (item) {
102865             index = me.indexOf(item);
102866             if (!record) {
102867                 record = me.getRecord(item);
102868             }
102869
102870             if (me.processItemEvent(record, item, index, e) === false) {
102871                 return false;
102872             }
102873
102874             newType = me.isNewItemEvent(item, e);
102875             if (newType === false) {
102876                 return false;
102877             }
102878
102879             if (
102880                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
102881                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
102882                 (me['onItem' + map[newType]](record, item, index, e) === false)
102883             ) {
102884                 return false;
102885             }
102886
102887             me.fireEvent('item' + newType, me, record, item, index, e);
102888         }
102889         else {
102890             if (
102891                 (me.processContainerEvent(e) === false) ||
102892                 (me['onBeforeContainer' + map[type]](e) === false) ||
102893                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
102894                 (me['onContainer' + map[type]](e) === false)
102895             ) {
102896                 return false;
102897             }
102898
102899             me.fireEvent('container' + type, me, e);
102900         }
102901
102902         return true;
102903     },
102904
102905     isNewItemEvent: function (item, e) {
102906         var me = this,
102907             overItem = me.mouseOverItem,
102908             type = e.type;
102909
102910         switch (type) {
102911             case 'mouseover':
102912                 if (item === overItem) {
102913                     return false;
102914                 }
102915                 me.mouseOverItem = item;
102916                 return 'mouseenter';
102917
102918             case 'mouseout':
102919                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
102920                 if (me.stillOverItem(e, overItem)) {
102921                     return false;
102922                 }
102923                 me.mouseOverItem = null;
102924                 return 'mouseleave';
102925         }
102926         return type;
102927     },
102928
102929     // private
102930     onItemMouseEnter: function(record, item, index, e) {
102931         if (this.trackOver) {
102932             this.highlightItem(item);
102933         }
102934     },
102935
102936     // private
102937     onItemMouseLeave : function(record, item, index, e) {
102938         if (this.trackOver) {
102939             this.clearHighlight();
102940         }
102941     },
102942
102943     // @private, template methods
102944     onItemMouseDown: Ext.emptyFn,
102945     onItemMouseUp: Ext.emptyFn,
102946     onItemFocus: Ext.emptyFn,
102947     onItemClick: Ext.emptyFn,
102948     onItemDblClick: Ext.emptyFn,
102949     onItemContextMenu: Ext.emptyFn,
102950     onItemKeyDown: Ext.emptyFn,
102951     onBeforeItemMouseDown: Ext.emptyFn,
102952     onBeforeItemMouseUp: Ext.emptyFn,
102953     onBeforeItemFocus: Ext.emptyFn,
102954     onBeforeItemMouseEnter: Ext.emptyFn,
102955     onBeforeItemMouseLeave: Ext.emptyFn,
102956     onBeforeItemClick: Ext.emptyFn,
102957     onBeforeItemDblClick: Ext.emptyFn,
102958     onBeforeItemContextMenu: Ext.emptyFn,
102959     onBeforeItemKeyDown: Ext.emptyFn,
102960
102961     // @private, template methods
102962     onContainerMouseDown: Ext.emptyFn,
102963     onContainerMouseUp: Ext.emptyFn,
102964     onContainerMouseOver: Ext.emptyFn,
102965     onContainerMouseOut: Ext.emptyFn,
102966     onContainerClick: Ext.emptyFn,
102967     onContainerDblClick: Ext.emptyFn,
102968     onContainerContextMenu: Ext.emptyFn,
102969     onContainerKeyDown: Ext.emptyFn,
102970     onBeforeContainerMouseDown: Ext.emptyFn,
102971     onBeforeContainerMouseUp: Ext.emptyFn,
102972     onBeforeContainerMouseOver: Ext.emptyFn,
102973     onBeforeContainerMouseOut: Ext.emptyFn,
102974     onBeforeContainerClick: Ext.emptyFn,
102975     onBeforeContainerDblClick: Ext.emptyFn,
102976     onBeforeContainerContextMenu: Ext.emptyFn,
102977     onBeforeContainerKeyDown: Ext.emptyFn,
102978
102979     /**
102980      * Highlights a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
102981      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
102982      * handle stepping through the list via keyboard navigation.
102983      * @param {HTMLElement} item The item to highlight
102984      */
102985     highlightItem: function(item) {
102986         var me = this;
102987         me.clearHighlight();
102988         me.highlightedItem = item;
102989         Ext.fly(item).addCls(me.overItemCls);
102990     },
102991
102992     /**
102993      * Un-highlights the currently highlighted item, if any.
102994      */
102995     clearHighlight: function() {
102996         var me = this,
102997             highlighted = me.highlightedItem;
102998
102999         if (highlighted) {
103000             Ext.fly(highlighted).removeCls(me.overItemCls);
103001             delete me.highlightedItem;
103002         }
103003     },
103004
103005     refresh: function() {
103006         var me = this;
103007         me.clearHighlight();
103008         me.callParent(arguments);
103009         if (!me.isFixedHeight()) {
103010             me.doComponentLayout();
103011         }
103012     }
103013 });
103014 /**
103015  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
103016  * @class Ext.layout.component.BoundList
103017  * @extends Ext.layout.component.Component
103018  * @private
103019  */
103020 Ext.define('Ext.layout.component.BoundList', {
103021     extend: 'Ext.layout.component.Component',
103022     alias: 'layout.boundlist',
103023
103024     type: 'component',
103025
103026     beforeLayout: function() {
103027         return this.callParent(arguments) || this.owner.refreshed > 0;
103028     },
103029
103030     onLayout : function(width, height) {
103031         var me = this,
103032             owner = me.owner,
103033             floating = owner.floating,
103034             el = owner.el,
103035             xy = el.getXY(),
103036             isNumber = Ext.isNumber,
103037             minWidth, maxWidth, minHeight, maxHeight,
103038             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
103039
103040         if (floating) {
103041             // Position offscreen so the natural width is not affected by the viewport's right edge
103042             el.setXY([-9999,-9999]);
103043         }
103044
103045         // Calculate initial layout
103046         me.setTargetSize(width, height);
103047
103048         // Handle min/maxWidth for auto-width
103049         if (!isNumber(width)) {
103050             minWidth = owner.minWidth;
103051             maxWidth = owner.maxWidth;
103052             if (isNumber(minWidth) || isNumber(maxWidth)) {
103053                 naturalWidth = el.getWidth();
103054                 if (naturalWidth < minWidth) {
103055                     constrainedWidth = minWidth;
103056                 }
103057                 else if (naturalWidth > maxWidth) {
103058                     constrainedWidth = maxWidth;
103059                 }
103060                 if (constrainedWidth) {
103061                     me.setTargetSize(constrainedWidth);
103062                 }
103063             }
103064         }
103065         // Handle min/maxHeight for auto-height
103066         if (!isNumber(height)) {
103067             minHeight = owner.minHeight;
103068             maxHeight = owner.maxHeight;
103069             if (isNumber(minHeight) || isNumber(maxHeight)) {
103070                 naturalHeight = el.getHeight();
103071                 if (naturalHeight < minHeight) {
103072                     constrainedHeight = minHeight;
103073                 }
103074                 else if (naturalHeight > maxHeight) {
103075                     constrainedHeight = maxHeight;
103076                 }
103077                 if (constrainedHeight) {
103078                     me.setTargetSize(undef, constrainedHeight);
103079                 }
103080             }
103081         }
103082
103083         if (floating) {
103084             // Restore position
103085             el.setXY(xy);
103086         }
103087     },
103088
103089     afterLayout: function() {
103090         var me = this,
103091             toolbar = me.owner.pagingToolbar;
103092         me.callParent();
103093         if (toolbar) {
103094             toolbar.doComponentLayout();
103095         }
103096     },
103097
103098     setTargetSize : function(width, height) {
103099         var me = this,
103100             owner = me.owner,
103101             listHeight = null,
103102             toolbar;
103103
103104         // Size the listEl
103105         if (Ext.isNumber(height)) {
103106             listHeight = height - owner.el.getFrameWidth('tb');
103107             toolbar = owner.pagingToolbar;
103108             if (toolbar) {
103109                 listHeight -= toolbar.getHeight();
103110             }
103111         }
103112         me.setElementSize(owner.listEl, null, listHeight);
103113
103114         me.callParent(arguments);
103115     }
103116
103117 });
103118
103119 /**
103120  * A simple class that renders text directly into a toolbar.
103121  *
103122  *     @example
103123  *     Ext.create('Ext.panel.Panel', {
103124  *         title: 'Panel with TextItem',
103125  *         width: 300,
103126  *         height: 200,
103127  *         tbar: [
103128  *             { xtype: 'tbtext', text: 'Sample Text Item' }
103129  *         ],
103130  *         renderTo: Ext.getBody()
103131  *     });
103132  *
103133  * @constructor
103134  * Creates a new TextItem
103135  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
103136  */
103137 Ext.define('Ext.toolbar.TextItem', {
103138     extend: 'Ext.toolbar.Item',
103139     requires: ['Ext.XTemplate'],
103140     alias: 'widget.tbtext',
103141     alternateClassName: 'Ext.Toolbar.TextItem',
103142
103143     /**
103144      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
103145      */
103146     text: '',
103147
103148     renderTpl: '{text}',
103149     //
103150     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
103151
103152     onRender : function() {
103153         Ext.apply(this.renderData, {
103154             text: this.text
103155         });
103156         this.callParent(arguments);
103157     },
103158
103159     /**
103160      * Updates this item's text, setting the text to be used as innerHTML.
103161      * @param {String} t The text to display (html accepted).
103162      */
103163     setText : function(t) {
103164         if (this.rendered) {
103165             this.el.update(t);
103166             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
103167         } else {
103168             this.text = t;
103169         }
103170     }
103171 });
103172 /**
103173  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
103174  * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
103175  * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
103176  * can provide a custom implementation.
103177  *
103178  * For example:
103179  *
103180  *     @example
103181  *     Ext.define('Ext.ux.CustomTrigger', {
103182  *         extend: 'Ext.form.field.Trigger',
103183  *         alias: 'widget.customtrigger',
103184  *
103185  *         // override onTriggerClick
103186  *         onTriggerClick: function() {
103187  *             Ext.Msg.alert('Status', 'You clicked my trigger!');
103188  *         }
103189  *     });
103190  *
103191  *     Ext.create('Ext.form.FormPanel', {
103192  *         title: 'Form with TriggerField',
103193  *         bodyPadding: 5,
103194  *         width: 350,
103195  *         renderTo: Ext.getBody(),
103196  *         items:[{
103197  *             xtype: 'customtrigger',
103198  *             fieldLabel: 'Sample Trigger',
103199  *             emptyText: 'click the trigger',
103200  *         }]
103201  *     });
103202  *
103203  * However, in general you will most likely want to use Trigger as the base class for a reusable component.
103204  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
103205  */
103206 Ext.define('Ext.form.field.Trigger', {
103207     extend:'Ext.form.field.Text',
103208     alias: ['widget.triggerfield', 'widget.trigger'],
103209     requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
103210     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
103211
103212     // note: {id} here is really {inputId}, but {cmpId} is available
103213     fieldSubTpl: [
103214         '<input id="{id}" type="{type}" ',
103215             '<tpl if="name">name="{name}" </tpl>',
103216             '<tpl if="size">size="{size}" </tpl>',
103217             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
103218             'class="{fieldCls} {typeCls}" autocomplete="off" />',
103219         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
103220             '{triggerEl}',
103221             '<div class="{clearCls}" role="presentation"></div>',
103222         '</div>',
103223         {
103224             compiled: true,
103225             disableFormats: true
103226         }
103227     ],
103228
103229     /**
103230      * @cfg {String} triggerCls
103231      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
103232      * by default and triggerCls will be **appended** if specified.
103233      */
103234
103235     /**
103236      * @cfg {String} [triggerBaseCls='x-form-trigger']
103237      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
103238      * addition to this class.
103239      */
103240     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
103241
103242     /**
103243      * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
103244      * The CSS class that is added to the div wrapping the trigger button(s).
103245      */
103246     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
103247
103248     /**
103249      * @cfg {Boolean} hideTrigger
103250      * true to hide the trigger element and display only the base text field
103251      */
103252     hideTrigger: false,
103253
103254     /**
103255      * @cfg {Boolean} editable
103256      * false to prevent the user from typing text directly into the field; the field can only have its value set via an
103257      * action invoked by the trigger.
103258      */
103259     editable: true,
103260
103261     /**
103262      * @cfg {Boolean} readOnly
103263      * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
103264      * options if the value is true.
103265      */
103266     readOnly: false,
103267
103268     /**
103269      * @cfg {Boolean} [selectOnFocus=false]
103270      * true to select any existing text in the field immediately on focus. Only applies when
103271      * {@link #editable editable} = true
103272      */
103273
103274     /**
103275      * @cfg {Boolean} repeatTriggerClick
103276      * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
103277      */
103278     repeatTriggerClick: false,
103279
103280
103281     /**
103282      * @hide
103283      * @method autoSize
103284      */
103285     autoSize: Ext.emptyFn,
103286     // private
103287     monitorTab: true,
103288     // private
103289     mimicing: false,
103290     // private
103291     triggerIndexRe: /trigger-index-(\d+)/,
103292
103293     componentLayout: 'triggerfield',
103294
103295     initComponent: function() {
103296         this.wrapFocusCls = this.triggerWrapCls + '-focus';
103297         this.callParent(arguments);
103298     },
103299
103300     // private
103301     onRender: function(ct, position) {
103302         var me = this,
103303             triggerCls,
103304             triggerBaseCls = me.triggerBaseCls,
103305             triggerWrapCls = me.triggerWrapCls,
103306             triggerConfigs = [],
103307             i;
103308
103309         // triggerCls is a synonym for trigger1Cls, so copy it.
103310         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
103311         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
103312         // trigger config objects that hold cls, handler, etc.
103313         if (!me.trigger1Cls) {
103314             me.trigger1Cls = me.triggerCls;
103315         }
103316
103317         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
103318         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
103319             triggerConfigs.push({
103320                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
103321                 role: 'button'
103322             });
103323         }
103324         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
103325
103326         /**
103327          * @property {Ext.Element} triggerWrap
103328          * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
103329          */
103330         me.addChildEls('triggerWrap');
103331
103332         Ext.applyIf(me.subTplData, {
103333             triggerWrapCls: triggerWrapCls,
103334             triggerEl: Ext.DomHelper.markup(triggerConfigs),
103335             clearCls: me.clearCls
103336         });
103337
103338         me.callParent(arguments);
103339
103340         /**
103341          * @property {Ext.CompositeElement} triggerEl
103342          * A composite of all the trigger button elements. Only set after the field has been rendered.
103343          */
103344         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
103345
103346         me.doc = Ext.getDoc();
103347         me.initTrigger();
103348     },
103349
103350     onEnable: function() {
103351         this.callParent();
103352         this.triggerWrap.unmask();
103353     },
103354     
103355     onDisable: function() {
103356         this.callParent();
103357         this.triggerWrap.mask();
103358     },
103359     
103360     afterRender: function() {
103361         this.callParent();
103362         this.updateEditState();
103363         this.triggerEl.unselectable();
103364     },
103365
103366     updateEditState: function() {
103367         var me = this,
103368             inputEl = me.inputEl,
103369             triggerWrap = me.triggerWrap,
103370             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
103371             displayed,
103372             readOnly;
103373
103374         if (me.rendered) {
103375             if (me.readOnly) {
103376                 inputEl.addCls(noeditCls);
103377                 readOnly = true;
103378                 displayed = false;
103379             } else {
103380                 if (me.editable) {
103381                     inputEl.removeCls(noeditCls);
103382                     readOnly = false;
103383                 } else {
103384                     inputEl.addCls(noeditCls);
103385                     readOnly = true;
103386                 }
103387                 displayed = !me.hideTrigger;
103388             }
103389
103390             triggerWrap.setDisplayed(displayed);
103391             inputEl.dom.readOnly = readOnly;
103392             me.doComponentLayout();
103393         }
103394     },
103395
103396     /**
103397      * Get the total width of the trigger button area. Only useful after the field has been rendered.
103398      * @return {Number} The trigger width
103399      */
103400     getTriggerWidth: function() {
103401         var me = this,
103402             triggerWrap = me.triggerWrap,
103403             totalTriggerWidth = 0;
103404         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
103405             me.triggerEl.each(function(trigger) {
103406                 totalTriggerWidth += trigger.getWidth();
103407             });
103408             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
103409         }
103410         return totalTriggerWidth;
103411     },
103412
103413     setHideTrigger: function(hideTrigger) {
103414         if (hideTrigger != this.hideTrigger) {
103415             this.hideTrigger = hideTrigger;
103416             this.updateEditState();
103417         }
103418     },
103419
103420     /**
103421      * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
103422      * option at config time.
103423      * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
103424      * will only be able to modify the field using the trigger. Will also add a click event to the text field which
103425      * will call the trigger. 
103426      */
103427     setEditable: function(editable) {
103428         if (editable != this.editable) {
103429             this.editable = editable;
103430             this.updateEditState();
103431         }
103432     },
103433
103434     /**
103435      * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
103436      * option at config time.
103437      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
103438      * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
103439      * and hideTrigger.
103440      */
103441     setReadOnly: function(readOnly) {
103442         if (readOnly != this.readOnly) {
103443             this.readOnly = readOnly;
103444             this.updateEditState();
103445         }
103446     },
103447
103448     // private
103449     initTrigger: function() {
103450         var me = this,
103451             triggerWrap = me.triggerWrap,
103452             triggerEl = me.triggerEl;
103453
103454         if (me.repeatTriggerClick) {
103455             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
103456                 preventDefault: true,
103457                 handler: function(cr, e) {
103458                     me.onTriggerWrapClick(e);
103459                 }
103460             });
103461         } else {
103462             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
103463         }
103464
103465         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
103466         triggerEl.each(function(el, c, i) {
103467             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
103468         });
103469         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
103470         triggerEl.each(function(el, c, i) {
103471             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
103472         });
103473     },
103474
103475     // private
103476     onDestroy: function() {
103477         var me = this;
103478         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
103479         delete me.doc;
103480         me.callParent();
103481     },
103482
103483     // private
103484     onFocus: function() {
103485         var me = this;
103486         me.callParent();
103487         if (!me.mimicing) {
103488             me.bodyEl.addCls(me.wrapFocusCls);
103489             me.mimicing = true;
103490             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
103491                 delay: 10
103492             });
103493             if (me.monitorTab) {
103494                 me.on('specialkey', me.checkTab, me);
103495             }
103496         }
103497     },
103498
103499     // private
103500     checkTab: function(me, e) {
103501         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
103502             this.triggerBlur();
103503         }
103504     },
103505
103506     // private
103507     onBlur: Ext.emptyFn,
103508
103509     // private
103510     mimicBlur: function(e) {
103511         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
103512             this.triggerBlur();
103513         }
103514     },
103515
103516     // private
103517     triggerBlur: function() {
103518         var me = this;
103519         me.mimicing = false;
103520         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
103521         if (me.monitorTab && me.inputEl) {
103522             me.un('specialkey', me.checkTab, me);
103523         }
103524         Ext.form.field.Trigger.superclass.onBlur.call(me);
103525         if (me.bodyEl) {
103526             me.bodyEl.removeCls(me.wrapFocusCls);
103527         }
103528     },
103529
103530     beforeBlur: Ext.emptyFn,
103531
103532     // private
103533     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
103534     validateBlur: function(e) {
103535         return true;
103536     },
103537
103538     // private
103539     // process clicks upon triggers.
103540     // determine which trigger index, and dispatch to the appropriate click handler
103541     onTriggerWrapClick: function(e) {
103542         var me = this,
103543             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
103544             match = t && t.className.match(me.triggerIndexRe),
103545             idx,
103546             triggerClickMethod;
103547
103548         if (match && !me.readOnly) {
103549             idx = parseInt(match[1], 10);
103550             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
103551             if (triggerClickMethod) {
103552                 triggerClickMethod.call(me, e);
103553             }
103554         }
103555     },
103556
103557     /**
103558      * @method onTriggerClick
103559      * @protected
103560      * The function that should handle the trigger's click event. This method does nothing by default until overridden
103561      * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
103562      * @param {Ext.EventObject} e
103563      */
103564     onTriggerClick: Ext.emptyFn
103565
103566     /**
103567      * @cfg {Boolean} grow @hide
103568      */
103569     /**
103570      * @cfg {Number} growMin @hide
103571      */
103572     /**
103573      * @cfg {Number} growMax @hide
103574      */
103575 });
103576
103577 /**
103578  * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
103579  * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
103580  * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
103581  * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
103582  * respectively.
103583  *
103584  * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
103585  * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
103586  * for the field.
103587  */
103588 Ext.define('Ext.form.field.Picker', {
103589     extend: 'Ext.form.field.Trigger',
103590     alias: 'widget.pickerfield',
103591     alternateClassName: 'Ext.form.Picker',
103592     requires: ['Ext.util.KeyNav'],
103593
103594     /**
103595      * @cfg {Boolean} matchFieldWidth
103596      * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
103597      */
103598     matchFieldWidth: true,
103599
103600     /**
103601      * @cfg {String} pickerAlign
103602      * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
103603      */
103604     pickerAlign: 'tl-bl?',
103605
103606     /**
103607      * @cfg {Number[]} pickerOffset
103608      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
103609      * Defaults to undefined.
103610      */
103611
103612     /**
103613      * @cfg {String} openCls
103614      * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
103615      * Defaults to 'x-pickerfield-open'.
103616      */
103617     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
103618
103619     /**
103620      * @property {Boolean} isExpanded
103621      * True if the picker is currently expanded, false if not.
103622      */
103623
103624     /**
103625      * @cfg {Boolean} editable
103626      * False to prevent the user from typing text directly into the field; the field can only have its value set via
103627      * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
103628      * field itself.
103629      */
103630     editable: true,
103631
103632
103633     initComponent: function() {
103634         this.callParent();
103635
103636         // Custom events
103637         this.addEvents(
103638             /**
103639              * @event expand
103640              * Fires when the field's picker is expanded.
103641              * @param {Ext.form.field.Picker} field This field instance
103642              */
103643             'expand',
103644             /**
103645              * @event collapse
103646              * Fires when the field's picker is collapsed.
103647              * @param {Ext.form.field.Picker} field This field instance
103648              */
103649             'collapse',
103650             /**
103651              * @event select
103652              * Fires when a value is selected via the picker.
103653              * @param {Ext.form.field.Picker} field This field instance
103654              * @param {Object} value The value that was selected. The exact type of this value is dependent on
103655              * the individual field and picker implementations.
103656              */
103657             'select'
103658         );
103659     },
103660
103661
103662     initEvents: function() {
103663         var me = this;
103664         me.callParent();
103665
103666         // Add handlers for keys to expand/collapse the picker
103667         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
103668             down: function() {
103669                 if (!me.isExpanded) {
103670                     // Don't call expand() directly as there may be additional processing involved before
103671                     // expanding, e.g. in the case of a ComboBox query.
103672                     me.onTriggerClick();
103673                 }
103674             },
103675             esc: me.collapse,
103676             scope: me,
103677             forceKeyDown: true
103678         });
103679
103680         // Non-editable allows opening the picker by clicking the field
103681         if (!me.editable) {
103682             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
103683         }
103684
103685         // Disable native browser autocomplete
103686         if (Ext.isGecko) {
103687             me.inputEl.dom.setAttribute('autocomplete', 'off');
103688         }
103689     },
103690
103691
103692     /**
103693      * Expands this field's picker dropdown.
103694      */
103695     expand: function() {
103696         var me = this,
103697             bodyEl, picker, collapseIf;
103698
103699         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
103700             bodyEl = me.bodyEl;
103701             picker = me.getPicker();
103702             collapseIf = me.collapseIf;
103703
103704             // show the picker and set isExpanded flag
103705             picker.show();
103706             me.isExpanded = true;
103707             me.alignPicker();
103708             bodyEl.addCls(me.openCls);
103709
103710             // monitor clicking and mousewheel
103711             me.mon(Ext.getDoc(), {
103712                 mousewheel: collapseIf,
103713                 mousedown: collapseIf,
103714                 scope: me
103715             });
103716             Ext.EventManager.onWindowResize(me.alignPicker, me);
103717             me.fireEvent('expand', me);
103718             me.onExpand();
103719         }
103720     },
103721
103722     onExpand: Ext.emptyFn,
103723
103724     /**
103725      * Aligns the picker to the input element
103726      * @protected
103727      */
103728     alignPicker: function() {
103729         var me = this,
103730             picker;
103731
103732         if (me.isExpanded) {
103733             picker = me.getPicker();
103734             if (me.matchFieldWidth) {
103735                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
103736                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
103737             }
103738             if (picker.isFloating()) {
103739                 me.doAlign();
103740             }
103741         }
103742     },
103743
103744     /**
103745      * Performs the alignment on the picker using the class defaults
103746      * @private
103747      */
103748     doAlign: function(){
103749         var me = this,
103750             picker = me.picker,
103751             aboveSfx = '-above',
103752             isAbove;
103753
103754         me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
103755         // add the {openCls}-above class if the picker was aligned above
103756         // the field due to hitting the bottom of the viewport
103757         isAbove = picker.el.getY() < me.inputEl.getY();
103758         me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
103759         picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
103760     },
103761
103762     /**
103763      * Collapses this field's picker dropdown.
103764      */
103765     collapse: function() {
103766         if (this.isExpanded && !this.isDestroyed) {
103767             var me = this,
103768                 openCls = me.openCls,
103769                 picker = me.picker,
103770                 doc = Ext.getDoc(),
103771                 collapseIf = me.collapseIf,
103772                 aboveSfx = '-above';
103773
103774             // hide the picker and set isExpanded flag
103775             picker.hide();
103776             me.isExpanded = false;
103777
103778             // remove the openCls
103779             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
103780             picker.el.removeCls(picker.baseCls + aboveSfx);
103781
103782             // remove event listeners
103783             doc.un('mousewheel', collapseIf, me);
103784             doc.un('mousedown', collapseIf, me);
103785             Ext.EventManager.removeResizeListener(me.alignPicker, me);
103786             me.fireEvent('collapse', me);
103787             me.onCollapse();
103788         }
103789     },
103790
103791     onCollapse: Ext.emptyFn,
103792
103793
103794     /**
103795      * @private
103796      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
103797      */
103798     collapseIf: function(e) {
103799         var me = this;
103800         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
103801             me.collapse();
103802         }
103803     },
103804
103805     /**
103806      * Returns a reference to the picker component for this field, creating it if necessary by
103807      * calling {@link #createPicker}.
103808      * @return {Ext.Component} The picker component
103809      */
103810     getPicker: function() {
103811         var me = this;
103812         return me.picker || (me.picker = me.createPicker());
103813     },
103814
103815     /**
103816      * @method
103817      * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
103818      * The current field should also be passed as a configuration option to the picker component as the pickerField
103819      * property.
103820      */
103821     createPicker: Ext.emptyFn,
103822
103823     /**
103824      * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
103825      * @protected
103826      */
103827     onTriggerClick: function() {
103828         var me = this;
103829         if (!me.readOnly && !me.disabled) {
103830             if (me.isExpanded) {
103831                 me.collapse();
103832             } else {
103833                 me.expand();
103834             }
103835             me.inputEl.focus();
103836         }
103837     },
103838
103839     mimicBlur: function(e) {
103840         var me = this,
103841             picker = me.picker;
103842         // ignore mousedown events within the picker element
103843         if (!picker || !e.within(picker.el, false, true)) {
103844             me.callParent(arguments);
103845         }
103846     },
103847
103848     onDestroy : function(){
103849         var me = this,
103850             picker = me.picker;
103851
103852         Ext.EventManager.removeResizeListener(me.alignPicker, me);
103853         Ext.destroy(me.keyNav);
103854         if (picker) {
103855             delete picker.pickerField;
103856             picker.destroy();
103857         }
103858         me.callParent();
103859     }
103860
103861 });
103862
103863
103864 /**
103865  * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
103866  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
103867  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
103868  * field which uses the spinner to increment and decrement the field's value by its
103869  * {@link Ext.form.field.Number#step step} config value.
103870  *
103871  * For example:
103872  *
103873  *     @example
103874  *     Ext.define('Ext.ux.CustomSpinner', {
103875  *         extend: 'Ext.form.field.Spinner',
103876  *         alias: 'widget.customspinner',
103877  *
103878  *         // override onSpinUp (using step isn't neccessary)
103879  *         onSpinUp: function() {
103880  *             var me = this;
103881  *             if (!me.readOnly) {
103882  *                 var val = me.step; // set the default value to the step value
103883  *                 if(me.getValue() !== '') {
103884  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103885  *                 }
103886  *                 me.setValue((val + me.step) + ' Pack');
103887  *             }
103888  *         },
103889  *
103890  *         // override onSpinDown
103891  *         onSpinDown: function() {
103892  *             var val, me = this;
103893  *             if (!me.readOnly) {
103894  *                 if(me.getValue() !== '') {
103895  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103896  *                 }
103897  *                 me.setValue((val - me.step) + ' Pack');
103898  *             }
103899  *         }
103900  *     });
103901  *
103902  *     Ext.create('Ext.form.FormPanel', {
103903  *         title: 'Form with SpinnerField',
103904  *         bodyPadding: 5,
103905  *         width: 350,
103906  *         renderTo: Ext.getBody(),
103907  *         items:[{
103908  *             xtype: 'customspinner',
103909  *             fieldLabel: 'How Much Beer?',
103910  *             step: 6
103911  *         }]
103912  *     });
103913  *
103914  * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
103915  * to prevent this, set `{@link #keyNavEnabled} = false`.
103916  */
103917 Ext.define('Ext.form.field.Spinner', {
103918     extend: 'Ext.form.field.Trigger',
103919     alias: 'widget.spinnerfield',
103920     alternateClassName: 'Ext.form.Spinner',
103921     requires: ['Ext.util.KeyNav'],
103922
103923     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
103924     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
103925
103926     /**
103927      * @cfg {Boolean} spinUpEnabled
103928      * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
103929      * created, use the {@link #setSpinUpEnabled} method.
103930      */
103931     spinUpEnabled: true,
103932
103933     /**
103934      * @cfg {Boolean} spinDownEnabled
103935      * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
103936      * created, use the {@link #setSpinDownEnabled} method.
103937      */
103938     spinDownEnabled: true,
103939
103940     /**
103941      * @cfg {Boolean} keyNavEnabled
103942      * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
103943      */
103944     keyNavEnabled: true,
103945
103946     /**
103947      * @cfg {Boolean} mouseWheelEnabled
103948      * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
103949      * Defaults to true.
103950      */
103951     mouseWheelEnabled: true,
103952
103953     /**
103954      * @cfg {Boolean} repeatTriggerClick
103955      * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
103956      * Defaults to true.
103957      */
103958     repeatTriggerClick: true,
103959
103960     /**
103961      * @method
103962      * @protected
103963      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
103964      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103965      */
103966     onSpinUp: Ext.emptyFn,
103967
103968     /**
103969      * @method
103970      * @protected
103971      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
103972      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103973      */
103974     onSpinDown: Ext.emptyFn,
103975
103976     initComponent: function() {
103977         this.callParent();
103978
103979         this.addEvents(
103980             /**
103981              * @event spin
103982              * Fires when the spinner is made to spin up or down.
103983              * @param {Ext.form.field.Spinner} this
103984              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
103985              */
103986             'spin',
103987
103988             /**
103989              * @event spinup
103990              * Fires when the spinner is made to spin up.
103991              * @param {Ext.form.field.Spinner} this
103992              */
103993             'spinup',
103994
103995             /**
103996              * @event spindown
103997              * Fires when the spinner is made to spin down.
103998              * @param {Ext.form.field.Spinner} this
103999              */
104000             'spindown'
104001         );
104002     },
104003
104004     /**
104005      * @private
104006      * Override.
104007      */
104008     onRender: function() {
104009         var me = this,
104010             triggers;
104011
104012         me.callParent(arguments);
104013         triggers = me.triggerEl;
104014
104015         /**
104016          * @property {Ext.Element} spinUpEl
104017          * The spinner up button element
104018          */
104019         me.spinUpEl = triggers.item(0);
104020         /**
104021          * @property {Ext.Element} spinDownEl
104022          * The spinner down button element
104023          */
104024         me.spinDownEl = triggers.item(1);
104025
104026         // Set initial enabled/disabled states
104027         me.setSpinUpEnabled(me.spinUpEnabled);
104028         me.setSpinDownEnabled(me.spinDownEnabled);
104029
104030         // Init up/down arrow keys
104031         if (me.keyNavEnabled) {
104032             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
104033                 scope: me,
104034                 up: me.spinUp,
104035                 down: me.spinDown
104036             });
104037         }
104038
104039         // Init mouse wheel
104040         if (me.mouseWheelEnabled) {
104041             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
104042         }
104043     },
104044
104045     /**
104046      * @private
104047      * Override. Since the triggers are stacked, only measure the width of one of them.
104048      */
104049     getTriggerWidth: function() {
104050         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
104051     },
104052
104053     /**
104054      * @private
104055      * Handles the spinner up button clicks.
104056      */
104057     onTrigger1Click: function() {
104058         this.spinUp();
104059     },
104060
104061     /**
104062      * @private
104063      * Handles the spinner down button clicks.
104064      */
104065     onTrigger2Click: function() {
104066         this.spinDown();
104067     },
104068
104069     /**
104070      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
104071      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
104072      * is false.
104073      */
104074     spinUp: function() {
104075         var me = this;
104076         if (me.spinUpEnabled && !me.disabled) {
104077             me.fireEvent('spin', me, 'up');
104078             me.fireEvent('spinup', me);
104079             me.onSpinUp();
104080         }
104081     },
104082
104083     /**
104084      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
104085      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
104086      * is false.
104087      */
104088     spinDown: function() {
104089         var me = this;
104090         if (me.spinDownEnabled && !me.disabled) {
104091             me.fireEvent('spin', me, 'down');
104092             me.fireEvent('spindown', me);
104093             me.onSpinDown();
104094         }
104095     },
104096
104097     /**
104098      * Sets whether the spinner up button is enabled.
104099      * @param {Boolean} enabled true to enable the button, false to disable it.
104100      */
104101     setSpinUpEnabled: function(enabled) {
104102         var me = this,
104103             wasEnabled = me.spinUpEnabled;
104104         me.spinUpEnabled = enabled;
104105         if (wasEnabled !== enabled && me.rendered) {
104106             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
104107         }
104108     },
104109
104110     /**
104111      * Sets whether the spinner down button is enabled.
104112      * @param {Boolean} enabled true to enable the button, false to disable it.
104113      */
104114     setSpinDownEnabled: function(enabled) {
104115         var me = this,
104116             wasEnabled = me.spinDownEnabled;
104117         me.spinDownEnabled = enabled;
104118         if (wasEnabled !== enabled && me.rendered) {
104119             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
104120         }
104121     },
104122
104123     /**
104124      * @private
104125      * Handles mousewheel events on the field
104126      */
104127     onMouseWheel: function(e) {
104128         var me = this,
104129             delta;
104130         if (me.hasFocus) {
104131             delta = e.getWheelDelta();
104132             if (delta > 0) {
104133                 me.spinUp();
104134             }
104135             else if (delta < 0) {
104136                 me.spinDown();
104137             }
104138             e.stopEvent();
104139         }
104140     },
104141
104142     onDestroy: function() {
104143         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
104144         this.callParent();
104145     }
104146
104147 });
104148 /**
104149  * @docauthor Jason Johnston <jason@sencha.com>
104150  *
104151  * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
104152  * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
104153  * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
104154  * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
104155  *
104156  * By default, the number field is also rendered with a set of up/down spinner buttons and has
104157  * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
104158  * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
104159  * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
104160  * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
104161  *
104162  * # Example usage
104163  *
104164  *     @example
104165  *     Ext.create('Ext.form.Panel', {
104166  *         title: 'On The Wall',
104167  *         width: 300,
104168  *         bodyPadding: 10,
104169  *         renderTo: Ext.getBody(),
104170  *         items: [{
104171  *             xtype: 'numberfield',
104172  *             anchor: '100%',
104173  *             name: 'bottles',
104174  *             fieldLabel: 'Bottles of Beer',
104175  *             value: 99,
104176  *             maxValue: 99,
104177  *             minValue: 0
104178  *         }],
104179  *         buttons: [{
104180  *             text: 'Take one down, pass it around',
104181  *             handler: function() {
104182  *                 this.up('form').down('[name=bottles]').spinDown();
104183  *             }
104184  *         }]
104185  *     });
104186  *
104187  * # Removing UI Enhancements
104188  *
104189  *     @example
104190  *     Ext.create('Ext.form.Panel', {
104191  *         title: 'Personal Info',
104192  *         width: 300,
104193  *         bodyPadding: 10,
104194  *         renderTo: Ext.getBody(),
104195  *         items: [{
104196  *             xtype: 'numberfield',
104197  *             anchor: '100%',
104198  *             name: 'age',
104199  *             fieldLabel: 'Age',
104200  *             minValue: 0, //prevents negative numbers
104201  *
104202  *             // Remove spinner buttons, and arrow key and mouse wheel listeners
104203  *             hideTrigger: true,
104204  *             keyNavEnabled: false,
104205  *             mouseWheelEnabled: false
104206  *         }]
104207  *     });
104208  *
104209  * # Using Step
104210  *
104211  *     @example
104212  *     Ext.create('Ext.form.Panel', {
104213  *         renderTo: Ext.getBody(),
104214  *         title: 'Step',
104215  *         width: 300,
104216  *         bodyPadding: 10,
104217  *         items: [{
104218  *             xtype: 'numberfield',
104219  *             anchor: '100%',
104220  *             name: 'evens',
104221  *             fieldLabel: 'Even Numbers',
104222  *
104223  *             // Set step so it skips every other number
104224  *             step: 2,
104225  *             value: 0,
104226  *
104227  *             // Add change handler to force user-entered numbers to evens
104228  *             listeners: {
104229  *                 change: function(field, value) {
104230  *                     value = parseInt(value, 10);
104231  *                     field.setValue(value + value % 2);
104232  *                 }
104233  *             }
104234  *         }]
104235  *     });
104236  */
104237 Ext.define('Ext.form.field.Number', {
104238     extend:'Ext.form.field.Spinner',
104239     alias: 'widget.numberfield',
104240     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
104241
104242     /**
104243      * @cfg {RegExp} stripCharsRe @hide
104244      */
104245     /**
104246      * @cfg {RegExp} maskRe @hide
104247      */
104248
104249     /**
104250      * @cfg {Boolean} allowDecimals
104251      * False to disallow decimal values
104252      */
104253     allowDecimals : true,
104254
104255     /**
104256      * @cfg {String} decimalSeparator
104257      * Character(s) to allow as the decimal separator
104258      */
104259     decimalSeparator : '.',
104260
104261     /**
104262      * @cfg {Number} decimalPrecision
104263      * The maximum precision to display after the decimal separator
104264      */
104265     decimalPrecision : 2,
104266
104267     /**
104268      * @cfg {Number} minValue
104269      * The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by the field's validation logic,
104270      * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
104271      */
104272     minValue: Number.NEGATIVE_INFINITY,
104273
104274     /**
104275      * @cfg {Number} maxValue
104276      * The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by the field's validation logic, and for
104277      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
104278      */
104279     maxValue: Number.MAX_VALUE,
104280
104281     /**
104282      * @cfg {Number} step
104283      * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
104284      * the spinner.
104285      */
104286     step: 1,
104287
104288     /**
104289      * @cfg {String} minText
104290      * Error text to display if the minimum value validation fails.
104291      */
104292     minText : 'The minimum value for this field is {0}',
104293
104294     /**
104295      * @cfg {String} maxText
104296      * Error text to display if the maximum value validation fails.
104297      */
104298     maxText : 'The maximum value for this field is {0}',
104299
104300     /**
104301      * @cfg {String} nanText
104302      * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
104303      * '.' or '-' is left in the field with no number.
104304      */
104305     nanText : '{0} is not a valid number',
104306
104307     /**
104308      * @cfg {String} negativeText
104309      * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
104310      * {@link #minText} in that circumstance only.
104311      */
104312     negativeText : 'The value cannot be negative',
104313
104314     /**
104315      * @cfg {String} baseChars
104316      * The base set of characters to evaluate as valid numbers.
104317      */
104318     baseChars : '0123456789',
104319
104320     /**
104321      * @cfg {Boolean} autoStripChars
104322      * True to automatically strip not allowed characters from the field.
104323      */
104324     autoStripChars: false,
104325
104326     initComponent: function() {
104327         var me = this,
104328             allowed;
104329
104330         me.callParent();
104331
104332         me.setMinValue(me.minValue);
104333         me.setMaxValue(me.maxValue);
104334
104335         // Build regexes for masking and stripping based on the configured options
104336         if (me.disableKeyFilter !== true) {
104337             allowed = me.baseChars + '';
104338             if (me.allowDecimals) {
104339                 allowed += me.decimalSeparator;
104340             }
104341             if (me.minValue < 0) {
104342                 allowed += '-';
104343             }
104344             allowed = Ext.String.escapeRegex(allowed);
104345             me.maskRe = new RegExp('[' + allowed + ']');
104346             if (me.autoStripChars) {
104347                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
104348             }
104349         }
104350     },
104351
104352     /**
104353      * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
104354      * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
104355      * that the value is a number, and that it is within the configured min and max values.
104356      * @param {Object} [value] The value to get errors for (defaults to the current field value)
104357      * @return {String[]} All validation errors for this field
104358      */
104359     getErrors: function(value) {
104360         var me = this,
104361             errors = me.callParent(arguments),
104362             format = Ext.String.format,
104363             num;
104364
104365         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
104366
104367         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
104368              return errors;
104369         }
104370
104371         value = String(value).replace(me.decimalSeparator, '.');
104372
104373         if(isNaN(value)){
104374             errors.push(format(me.nanText, value));
104375         }
104376
104377         num = me.parseValue(value);
104378
104379         if (me.minValue === 0 && num < 0) {
104380             errors.push(this.negativeText);
104381         }
104382         else if (num < me.minValue) {
104383             errors.push(format(me.minText, me.minValue));
104384         }
104385
104386         if (num > me.maxValue) {
104387             errors.push(format(me.maxText, me.maxValue));
104388         }
104389
104390
104391         return errors;
104392     },
104393
104394     rawToValue: function(rawValue) {
104395         var value = this.fixPrecision(this.parseValue(rawValue));
104396         if (value === null) {
104397             value = rawValue || null;
104398         }
104399         return  value;
104400     },
104401
104402     valueToRaw: function(value) {
104403         var me = this,
104404             decimalSeparator = me.decimalSeparator;
104405         value = me.parseValue(value);
104406         value = me.fixPrecision(value);
104407         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
104408         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
104409         return value;
104410     },
104411
104412     onChange: function() {
104413         var me = this,
104414             value = me.getValue(),
104415             valueIsNull = value === null;
104416
104417         me.callParent(arguments);
104418
104419         // Update the spinner buttons
104420         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
104421         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
104422     },
104423
104424     /**
104425      * Replaces any existing {@link #minValue} with the new value.
104426      * @param {Number} value The minimum value
104427      */
104428     setMinValue : function(value) {
104429         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
104430     },
104431
104432     /**
104433      * Replaces any existing {@link #maxValue} with the new value.
104434      * @param {Number} value The maximum value
104435      */
104436     setMaxValue: function(value) {
104437         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
104438     },
104439
104440     // private
104441     parseValue : function(value) {
104442         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
104443         return isNaN(value) ? null : value;
104444     },
104445
104446     /**
104447      * @private
104448      */
104449     fixPrecision : function(value) {
104450         var me = this,
104451             nan = isNaN(value),
104452             precision = me.decimalPrecision;
104453
104454         if (nan || !value) {
104455             return nan ? '' : value;
104456         } else if (!me.allowDecimals || precision <= 0) {
104457             precision = 0;
104458         }
104459
104460         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
104461     },
104462
104463     beforeBlur : function() {
104464         var me = this,
104465             v = me.parseValue(me.getRawValue());
104466
104467         if (!Ext.isEmpty(v)) {
104468             me.setValue(v);
104469         }
104470     },
104471
104472     onSpinUp: function() {
104473         var me = this;
104474         if (!me.readOnly) {
104475             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
104476         }
104477     },
104478
104479     onSpinDown: function() {
104480         var me = this;
104481         if (!me.readOnly) {
104482             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
104483         }
104484     }
104485 });
104486
104487 /**
104488  * As the number of records increases, the time required for the browser to render them increases. Paging is used to
104489  * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
104490  * available screen area, vertical scrollbars will be added.
104491  *
104492  * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
104493  * which the server needs to interpret and then respond with the appropriate data.
104494  *
104495  * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
104496  * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing
104497  * parameters used for paging criteria.
104498  *
104499  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
104500  *
104501  * Paging Toolbar is typically used as one of the Grid's toolbars:
104502  *
104503  *     @example
104504  *     var itemsPerPage = 2;   // set the number of items you want per page
104505  *
104506  *     var store = Ext.create('Ext.data.Store', {
104507  *         id:'simpsonsStore',
104508  *         autoLoad: false,
104509  *         fields:['name', 'email', 'phone'],
104510  *         pageSize: itemsPerPage, // items per page
104511  *         proxy: {
104512  *             type: 'ajax',
104513  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
104514  *             reader: {
104515  *                 type: 'json',
104516  *                 root: 'items',
104517  *                 totalProperty: 'total'
104518  *             }
104519  *         }
104520  *     });
104521  *
104522  *     // specify segment of data you want to load using params
104523  *     store.load({
104524  *         params:{
104525  *             start:0,
104526  *             limit: itemsPerPage
104527  *         }
104528  *     });
104529  *
104530  *     Ext.create('Ext.grid.Panel', {
104531  *         title: 'Simpsons',
104532  *         store: store,
104533  *         columns: [
104534  *             { header: 'Name',  dataIndex: 'name' },
104535  *             { header: 'Email', dataIndex: 'email', flex: 1 },
104536  *             { header: 'Phone', dataIndex: 'phone' }
104537  *         ],
104538  *         width: 400,
104539  *         height: 125,
104540  *         dockedItems: [{
104541  *             xtype: 'pagingtoolbar',
104542  *             store: store,   // same store GridPanel is using
104543  *             dock: 'bottom',
104544  *             displayInfo: true
104545  *         }],
104546  *         renderTo: Ext.getBody()
104547  *     });
104548  *
104549  * To use paging, pass the paging requirements to the server when the store is first loaded.
104550  *
104551  *     store.load({
104552  *         params: {
104553  *             // specify params for the first page load if using paging
104554  *             start: 0,
104555  *             limit: myPageSize,
104556  *             // other params
104557  *             foo:   'bar'
104558  *         }
104559  *     });
104560  *
104561  * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
104562  *
104563  *     var myStore = Ext.create('Ext.data.Store', {
104564  *         {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
104565  *         ...
104566  *     });
104567  *
104568  * The packet sent back from the server would have this form:
104569  *
104570  *     {
104571  *         "success": true,
104572  *         "results": 2000,
104573  *         "rows": [ // ***Note:** this must be an Array
104574  *             { "id":  1, "name": "Bill", "occupation": "Gardener" },
104575  *             { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
104576  *             ...
104577  *             { "id": 25, "name":  "Sue", "occupation": "Botanist" }
104578  *         ]
104579  *     }
104580  *
104581  * ## Paging with Local Data
104582  *
104583  * Paging can also be accomplished with local data using extensions:
104584  *
104585  *   - [Ext.ux.data.PagingStore][1]
104586  *   - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
104587  *
104588  *    [1]: http://sencha.com/forum/showthread.php?t=71532
104589  */
104590 Ext.define('Ext.toolbar.Paging', {
104591     extend: 'Ext.toolbar.Toolbar',
104592     alias: 'widget.pagingtoolbar',
104593     alternateClassName: 'Ext.PagingToolbar',
104594     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
104595     /**
104596      * @cfg {Ext.data.Store} store (required)
104597      * The {@link Ext.data.Store} the paging toolbar should use as its data source.
104598      */
104599
104600     /**
104601      * @cfg {Boolean} displayInfo
104602      * true to display the displayMsg
104603      */
104604     displayInfo: false,
104605
104606     /**
104607      * @cfg {Boolean} prependButtons
104608      * true to insert any configured items _before_ the paging buttons.
104609      */
104610     prependButtons: false,
104611
104612     /**
104613      * @cfg {String} displayMsg
104614      * The paging status message to display. Note that this string is
104615      * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
104616      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
104617      */
104618     displayMsg : 'Displaying {0} - {1} of {2}',
104619
104620     /**
104621      * @cfg {String} emptyMsg
104622      * The message to display when no records are found.
104623      */
104624     emptyMsg : 'No data to display',
104625
104626     /**
104627      * @cfg {String} beforePageText
104628      * The text displayed before the input item.
104629      */
104630     beforePageText : 'Page',
104631
104632     /**
104633      * @cfg {String} afterPageText
104634      * Customizable piece of the default paging text. Note that this string is formatted using
104635      * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
104636      * string if showing the total page count is desired.
104637      */
104638     afterPageText : 'of {0}',
104639
104640     /**
104641      * @cfg {String} firstText
104642      * The quicktip text displayed for the first page button.
104643      * **Note**: quick tips must be initialized for the quicktip to show.
104644      */
104645     firstText : 'First Page',
104646
104647     /**
104648      * @cfg {String} prevText
104649      * The quicktip text displayed for the previous page button.
104650      * **Note**: quick tips must be initialized for the quicktip to show.
104651      */
104652     prevText : 'Previous Page',
104653
104654     /**
104655      * @cfg {String} nextText
104656      * The quicktip text displayed for the next page button.
104657      * **Note**: quick tips must be initialized for the quicktip to show.
104658      */
104659     nextText : 'Next Page',
104660
104661     /**
104662      * @cfg {String} lastText
104663      * The quicktip text displayed for the last page button.
104664      * **Note**: quick tips must be initialized for the quicktip to show.
104665      */
104666     lastText : 'Last Page',
104667
104668     /**
104669      * @cfg {String} refreshText
104670      * The quicktip text displayed for the Refresh button.
104671      * **Note**: quick tips must be initialized for the quicktip to show.
104672      */
104673     refreshText : 'Refresh',
104674
104675     /**
104676      * @cfg {Number} inputItemWidth
104677      * The width in pixels of the input field used to display and change the current page number.
104678      */
104679     inputItemWidth : 30,
104680
104681     /**
104682      * Gets the standard paging items in the toolbar
104683      * @private
104684      */
104685     getPagingItems: function() {
104686         var me = this;
104687
104688         return [{
104689             itemId: 'first',
104690             tooltip: me.firstText,
104691             overflowText: me.firstText,
104692             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
104693             disabled: true,
104694             handler: me.moveFirst,
104695             scope: me
104696         },{
104697             itemId: 'prev',
104698             tooltip: me.prevText,
104699             overflowText: me.prevText,
104700             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
104701             disabled: true,
104702             handler: me.movePrevious,
104703             scope: me
104704         },
104705         '-',
104706         me.beforePageText,
104707         {
104708             xtype: 'numberfield',
104709             itemId: 'inputItem',
104710             name: 'inputItem',
104711             cls: Ext.baseCSSPrefix + 'tbar-page-number',
104712             allowDecimals: false,
104713             minValue: 1,
104714             hideTrigger: true,
104715             enableKeyEvents: true,
104716             selectOnFocus: true,
104717             submitValue: false,
104718             width: me.inputItemWidth,
104719             margins: '-1 2 3 2',
104720             listeners: {
104721                 scope: me,
104722                 keydown: me.onPagingKeyDown,
104723                 blur: me.onPagingBlur
104724             }
104725         },{
104726             xtype: 'tbtext',
104727             itemId: 'afterTextItem',
104728             text: Ext.String.format(me.afterPageText, 1)
104729         },
104730         '-',
104731         {
104732             itemId: 'next',
104733             tooltip: me.nextText,
104734             overflowText: me.nextText,
104735             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
104736             disabled: true,
104737             handler: me.moveNext,
104738             scope: me
104739         },{
104740             itemId: 'last',
104741             tooltip: me.lastText,
104742             overflowText: me.lastText,
104743             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
104744             disabled: true,
104745             handler: me.moveLast,
104746             scope: me
104747         },
104748         '-',
104749         {
104750             itemId: 'refresh',
104751             tooltip: me.refreshText,
104752             overflowText: me.refreshText,
104753             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
104754             handler: me.doRefresh,
104755             scope: me
104756         }];
104757     },
104758
104759     initComponent : function(){
104760         var me = this,
104761             pagingItems = me.getPagingItems(),
104762             userItems   = me.items || me.buttons || [];
104763
104764         if (me.prependButtons) {
104765             me.items = userItems.concat(pagingItems);
104766         } else {
104767             me.items = pagingItems.concat(userItems);
104768         }
104769         delete me.buttons;
104770
104771         if (me.displayInfo) {
104772             me.items.push('->');
104773             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
104774         }
104775
104776         me.callParent();
104777
104778         me.addEvents(
104779             /**
104780              * @event change
104781              * Fires after the active page has been changed.
104782              * @param {Ext.toolbar.Paging} this
104783              * @param {Object} pageData An object that has these properties:
104784              *
104785              * - `total` : Number
104786              *
104787              *   The total number of records in the dataset as returned by the server
104788              *
104789              * - `currentPage` : Number
104790              *
104791              *   The current page number
104792              *
104793              * - `pageCount` : Number
104794              *
104795              *   The total number of pages (calculated from the total number of records in the dataset as returned by the
104796              *   server and the current {@link Ext.data.Store#pageSize pageSize})
104797              *
104798              * - `toRecord` : Number
104799              *
104800              *   The starting record index for the current page
104801              *
104802              * - `fromRecord` : Number
104803              *
104804              *   The ending record index for the current page
104805              */
104806             'change',
104807
104808             /**
104809              * @event beforechange
104810              * Fires just before the active page is changed. Return false to prevent the active page from being changed.
104811              * @param {Ext.toolbar.Paging} this
104812              * @param {Number} page The page number that will be loaded on change
104813              */
104814             'beforechange'
104815         );
104816         me.on('afterlayout', me.onLoad, me, {single: true});
104817
104818         me.bindStore(me.store || 'ext-empty-store', true);
104819     },
104820     // private
104821     updateInfo : function(){
104822         var me = this,
104823             displayItem = me.child('#displayItem'),
104824             store = me.store,
104825             pageData = me.getPageData(),
104826             count, msg;
104827
104828         if (displayItem) {
104829             count = store.getCount();
104830             if (count === 0) {
104831                 msg = me.emptyMsg;
104832             } else {
104833                 msg = Ext.String.format(
104834                     me.displayMsg,
104835                     pageData.fromRecord,
104836                     pageData.toRecord,
104837                     pageData.total
104838                 );
104839             }
104840             displayItem.setText(msg);
104841             me.doComponentLayout();
104842         }
104843     },
104844
104845     // private
104846     onLoad : function(){
104847         var me = this,
104848             pageData,
104849             currPage,
104850             pageCount,
104851             afterText;
104852
104853         if (!me.rendered) {
104854             return;
104855         }
104856
104857         pageData = me.getPageData();
104858         currPage = pageData.currentPage;
104859         pageCount = pageData.pageCount;
104860         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
104861
104862         me.child('#afterTextItem').setText(afterText);
104863         me.child('#inputItem').setValue(currPage);
104864         me.child('#first').setDisabled(currPage === 1);
104865         me.child('#prev').setDisabled(currPage === 1);
104866         me.child('#next').setDisabled(currPage === pageCount);
104867         me.child('#last').setDisabled(currPage === pageCount);
104868         me.child('#refresh').enable();
104869         me.updateInfo();
104870         me.fireEvent('change', me, pageData);
104871     },
104872
104873     // private
104874     getPageData : function(){
104875         var store = this.store,
104876             totalCount = store.getTotalCount();
104877
104878         return {
104879             total : totalCount,
104880             currentPage : store.currentPage,
104881             pageCount: Math.ceil(totalCount / store.pageSize),
104882             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
104883             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
104884
104885         };
104886     },
104887
104888     // private
104889     onLoadError : function(){
104890         if (!this.rendered) {
104891             return;
104892         }
104893         this.child('#refresh').enable();
104894     },
104895
104896     // private
104897     readPageFromInput : function(pageData){
104898         var v = this.child('#inputItem').getValue(),
104899             pageNum = parseInt(v, 10);
104900
104901         if (!v || isNaN(pageNum)) {
104902             this.child('#inputItem').setValue(pageData.currentPage);
104903             return false;
104904         }
104905         return pageNum;
104906     },
104907
104908     onPagingFocus : function(){
104909         this.child('#inputItem').select();
104910     },
104911
104912     //private
104913     onPagingBlur : function(e){
104914         var curPage = this.getPageData().currentPage;
104915         this.child('#inputItem').setValue(curPage);
104916     },
104917
104918     // private
104919     onPagingKeyDown : function(field, e){
104920         var me = this,
104921             k = e.getKey(),
104922             pageData = me.getPageData(),
104923             increment = e.shiftKey ? 10 : 1,
104924             pageNum;
104925
104926         if (k == e.RETURN) {
104927             e.stopEvent();
104928             pageNum = me.readPageFromInput(pageData);
104929             if (pageNum !== false) {
104930                 pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
104931                 if(me.fireEvent('beforechange', me, pageNum) !== false){
104932                     me.store.loadPage(pageNum);
104933                 }
104934             }
104935         } else if (k == e.HOME || k == e.END) {
104936             e.stopEvent();
104937             pageNum = k == e.HOME ? 1 : pageData.pageCount;
104938             field.setValue(pageNum);
104939         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
104940             e.stopEvent();
104941             pageNum = me.readPageFromInput(pageData);
104942             if (pageNum) {
104943                 if (k == e.DOWN || k == e.PAGEDOWN) {
104944                     increment *= -1;
104945                 }
104946                 pageNum += increment;
104947                 if (pageNum >= 1 && pageNum <= pageData.pages) {
104948                     field.setValue(pageNum);
104949                 }
104950             }
104951         }
104952     },
104953
104954     // private
104955     beforeLoad : function(){
104956         if(this.rendered && this.refresh){
104957             this.refresh.disable();
104958         }
104959     },
104960
104961     // private
104962     doLoad : function(start){
104963         if(this.fireEvent('beforechange', this, o) !== false){
104964             this.store.load();
104965         }
104966     },
104967
104968     /**
104969      * Move to the first page, has the same effect as clicking the 'first' button.
104970      */
104971     moveFirst : function(){
104972         if (this.fireEvent('beforechange', this, 1) !== false){
104973             this.store.loadPage(1);
104974         }
104975     },
104976
104977     /**
104978      * Move to the previous page, has the same effect as clicking the 'previous' button.
104979      */
104980     movePrevious : function(){
104981         var me = this,
104982             prev = me.store.currentPage - 1;
104983
104984         if (prev > 0) {
104985             if (me.fireEvent('beforechange', me, prev) !== false) {
104986                 me.store.previousPage();
104987             }
104988         }
104989     },
104990
104991     /**
104992      * Move to the next page, has the same effect as clicking the 'next' button.
104993      */
104994     moveNext : function(){
104995         var me = this,
104996             total = me.getPageData().pageCount,
104997             next = me.store.currentPage + 1;
104998
104999         if (next <= total) {
105000             if (me.fireEvent('beforechange', me, next) !== false) {
105001                 me.store.nextPage();
105002             }
105003         }
105004     },
105005
105006     /**
105007      * Move to the last page, has the same effect as clicking the 'last' button.
105008      */
105009     moveLast : function(){
105010         var me = this,
105011             last = me.getPageData().pageCount;
105012
105013         if (me.fireEvent('beforechange', me, last) !== false) {
105014             me.store.loadPage(last);
105015         }
105016     },
105017
105018     /**
105019      * Refresh the current page, has the same effect as clicking the 'refresh' button.
105020      */
105021     doRefresh : function(){
105022         var me = this,
105023             current = me.store.currentPage;
105024
105025         if (me.fireEvent('beforechange', me, current) !== false) {
105026             me.store.loadPage(current);
105027         }
105028     },
105029
105030     /**
105031      * Binds the paging toolbar to the specified {@link Ext.data.Store}
105032      * @param {Ext.data.Store} store The store to bind to this toolbar
105033      * @param {Boolean} initial (Optional) true to not remove listeners
105034      */
105035     bindStore : function(store, initial){
105036         var me = this;
105037
105038         if (!initial && me.store) {
105039             if(store !== me.store && me.store.autoDestroy){
105040                 me.store.destroyStore();
105041             }else{
105042                 me.store.un('beforeload', me.beforeLoad, me);
105043                 me.store.un('load', me.onLoad, me);
105044                 me.store.un('exception', me.onLoadError, me);
105045             }
105046             if(!store){
105047                 me.store = null;
105048             }
105049         }
105050         if (store) {
105051             store = Ext.data.StoreManager.lookup(store);
105052             store.on({
105053                 scope: me,
105054                 beforeload: me.beforeLoad,
105055                 load: me.onLoad,
105056                 exception: me.onLoadError
105057             });
105058         }
105059         me.store = store;
105060     },
105061
105062     /**
105063      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
105064      * @param {Ext.data.Store} store The data store to unbind
105065      */
105066     unbind : function(store){
105067         this.bindStore(null);
105068     },
105069
105070     /**
105071      * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
105072      * @param {Ext.data.Store} store The data store to bind
105073      */
105074     bind : function(store){
105075         this.bindStore(store);
105076     },
105077
105078     // private
105079     onDestroy : function(){
105080         this.bindStore(null);
105081         this.callParent();
105082     }
105083 });
105084
105085 /**
105086  * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
105087  */
105088 Ext.define('Ext.view.BoundList', {
105089     extend: 'Ext.view.View',
105090     alias: 'widget.boundlist',
105091     alternateClassName: 'Ext.BoundList',
105092     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
105093
105094     /**
105095      * @cfg {Number} pageSize
105096      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
105097      * queries will execute with page {@link Ext.data.Operation#start start} and
105098      * {@link Ext.data.Operation#limit limit} parameters. Defaults to `0`.
105099      */
105100     pageSize: 0,
105101
105102     /**
105103      * @property {Ext.toolbar.Paging} pagingToolbar
105104      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
105105      * than zero and the BoundList has been rendered.
105106      */
105107
105108     // private overrides
105109     autoScroll: true,
105110     baseCls: Ext.baseCSSPrefix + 'boundlist',
105111     itemCls: Ext.baseCSSPrefix + 'boundlist-item',
105112     listItemCls: '',
105113     shadow: false,
105114     trackOver: true,
105115     refreshed: 0,
105116
105117     ariaRole: 'listbox',
105118
105119     componentLayout: 'boundlist',
105120
105121     renderTpl: ['<div id="{id}-listEl" class="list-ct"></div>'],
105122
105123     initComponent: function() {
105124         var me = this,
105125             baseCls = me.baseCls,
105126             itemCls = me.itemCls;
105127             
105128         me.selectedItemCls = baseCls + '-selected';
105129         me.overItemCls = baseCls + '-item-over';
105130         me.itemSelector = "." + itemCls;
105131
105132         if (me.floating) {
105133             me.addCls(baseCls + '-floating');
105134         }
105135
105136         if (!me.tpl) {
105137             // should be setting aria-posinset based on entire set of data
105138             // not filtered set
105139             me.tpl = Ext.create('Ext.XTemplate',
105140                 '<ul><tpl for=".">',
105141                     '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
105142                 '</tpl></ul>'
105143             );
105144         } else if (Ext.isString(me.tpl)) {
105145             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
105146         }
105147
105148         if (me.pageSize) {
105149             me.pagingToolbar = me.createPagingToolbar();
105150         }
105151
105152         me.callParent();
105153
105154         me.addChildEls('listEl');
105155     },
105156
105157     createPagingToolbar: function() {
105158         return Ext.widget('pagingtoolbar', {
105159             pageSize: this.pageSize,
105160             store: this.store,
105161             border: false
105162         });
105163     },
105164
105165     onRender: function() {
105166         var me = this,
105167             toolbar = me.pagingToolbar;
105168         me.callParent(arguments);
105169         if (toolbar) {
105170             toolbar.render(me.el);
105171         }
105172     },
105173
105174     bindStore : function(store, initial) {
105175         var me = this,
105176             toolbar = me.pagingToolbar;
105177         me.callParent(arguments);
105178         if (toolbar) {
105179             toolbar.bindStore(store, initial);
105180         }
105181     },
105182
105183     getTargetEl: function() {
105184         return this.listEl || this.el;
105185     },
105186
105187     getInnerTpl: function(displayField) {
105188         return '{' + displayField + '}';
105189     },
105190
105191     refresh: function() {
105192         var me = this;
105193         me.callParent();
105194         if (me.isVisible()) {
105195             me.refreshed++;
105196             me.doComponentLayout();
105197             me.refreshed--;
105198         }
105199     },
105200
105201     initAria: function() {
105202         this.callParent();
105203
105204         var selModel = this.getSelectionModel(),
105205             mode     = selModel.getSelectionMode(),
105206             actionEl = this.getActionEl();
105207
105208         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
105209         if (mode !== 'SINGLE') {
105210             actionEl.dom.setAttribute('aria-multiselectable', true);
105211         }
105212     },
105213
105214     onDestroy: function() {
105215         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
105216         this.callParent();
105217     }
105218 });
105219
105220 /**
105221  * @class Ext.view.BoundListKeyNav
105222  * @extends Ext.util.KeyNav
105223  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
105224  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
105225  * through the list. The enter key invokes the selection model's select action using the highlighted item.
105226  */
105227 Ext.define('Ext.view.BoundListKeyNav', {
105228     extend: 'Ext.util.KeyNav',
105229     requires: 'Ext.view.BoundList',
105230
105231     /**
105232      * @cfg {Ext.view.BoundList} boundList (required)
105233      * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
105234      */
105235
105236     constructor: function(el, config) {
105237         var me = this;
105238         me.boundList = config.boundList;
105239         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
105240     },
105241
105242     defaultHandlers: {
105243         up: function() {
105244             var me = this,
105245                 boundList = me.boundList,
105246                 allItems = boundList.all,
105247                 oldItem = boundList.highlightedItem,
105248                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
105249                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
105250             me.highlightAt(newItemIdx);
105251         },
105252
105253         down: function() {
105254             var me = this,
105255                 boundList = me.boundList,
105256                 allItems = boundList.all,
105257                 oldItem = boundList.highlightedItem,
105258                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
105259                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
105260             me.highlightAt(newItemIdx);
105261         },
105262
105263         pageup: function() {
105264             //TODO
105265         },
105266
105267         pagedown: function() {
105268             //TODO
105269         },
105270
105271         home: function() {
105272             this.highlightAt(0);
105273         },
105274
105275         end: function() {
105276             var me = this;
105277             me.highlightAt(me.boundList.all.getCount() - 1);
105278         },
105279
105280         enter: function(e) {
105281             this.selectHighlighted(e);
105282         }
105283     },
105284
105285     /**
105286      * Highlights the item at the given index.
105287      * @param {Number} index
105288      */
105289     highlightAt: function(index) {
105290         var boundList = this.boundList,
105291             item = boundList.all.item(index);
105292         if (item) {
105293             item = item.dom;
105294             boundList.highlightItem(item);
105295             boundList.getTargetEl().scrollChildIntoView(item, false);
105296         }
105297     },
105298
105299     /**
105300      * Triggers selection of the currently highlighted item according to the behavior of
105301      * the configured SelectionModel.
105302      */
105303     selectHighlighted: function(e) {
105304         var me = this,
105305             boundList = me.boundList,
105306             highlighted = boundList.highlightedItem,
105307             selModel = boundList.getSelectionModel();
105308         if (highlighted) {
105309             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
105310         }
105311     }
105312
105313 });
105314 /**
105315  * @docauthor Jason Johnston <jason@sencha.com>
105316  *
105317  * A combobox control with support for autocomplete, remote loading, and many other features.
105318  *
105319  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
105320  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
105321  * list. The user can input any value by default, even if it does not appear in the selection list;
105322  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
105323  *
105324  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
105325  * stores. The data items in the store are mapped to each option's displayed text and backing value via
105326  * the {@link #valueField} and {@link #displayField} configurations, respectively.
105327  *
105328  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
105329  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
105330  *
105331  * # Example usage:
105332  *
105333  *     @example
105334  *     // The data store containing the list of states
105335  *     var states = Ext.create('Ext.data.Store', {
105336  *         fields: ['abbr', 'name'],
105337  *         data : [
105338  *             {"abbr":"AL", "name":"Alabama"},
105339  *             {"abbr":"AK", "name":"Alaska"},
105340  *             {"abbr":"AZ", "name":"Arizona"}
105341  *             //...
105342  *         ]
105343  *     });
105344  *
105345  *     // Create the combo box, attached to the states data store
105346  *     Ext.create('Ext.form.ComboBox', {
105347  *         fieldLabel: 'Choose State',
105348  *         store: states,
105349  *         queryMode: 'local',
105350  *         displayField: 'name',
105351  *         valueField: 'abbr',
105352  *         renderTo: Ext.getBody()
105353  *     });
105354  *
105355  * # Events
105356  *
105357  * To do something when something in ComboBox is selected, configure the select event:
105358  *
105359  *     var cb = Ext.create('Ext.form.ComboBox', {
105360  *         // all of your config options
105361  *         listeners:{
105362  *              scope: yourScope,
105363  *              'select': yourFunction
105364  *         }
105365  *     });
105366  *
105367  *     // Alternatively, you can assign events after the object is created:
105368  *     var cb = new Ext.form.field.ComboBox(yourOptions);
105369  *     cb.on('select', yourFunction, yourScope);
105370  *
105371  * # Multiple Selection
105372  *
105373  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
105374  * {@link #multiSelect} config to `true`.
105375  */
105376 Ext.define('Ext.form.field.ComboBox', {
105377     extend:'Ext.form.field.Picker',
105378     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
105379     alternateClassName: 'Ext.form.ComboBox',
105380     alias: ['widget.combobox', 'widget.combo'],
105381
105382     /**
105383      * @cfg {String} [triggerCls='x-form-arrow-trigger']
105384      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
105385      * by default and `triggerCls` will be **appended** if specified.
105386      */
105387     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
105388
105389     /**
105390      * @private
105391      * @cfg {String}
105392      * CSS class used to find the {@link #hiddenDataEl}
105393      */
105394     hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
105395
105396     /**
105397      * @override
105398      */
105399     fieldSubTpl: [
105400         '<div class="{hiddenDataCls}" role="presentation"></div>',
105401         '<input id="{id}" type="{type}" ',
105402             '<tpl if="size">size="{size}" </tpl>',
105403             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
105404             'class="{fieldCls} {typeCls}" autocomplete="off" />',
105405         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
105406             '{triggerEl}',
105407             '<div class="{clearCls}" role="presentation"></div>',
105408         '</div>',
105409         {
105410             compiled: true,
105411             disableFormats: true
105412         }
105413     ],
105414
105415     getSubTplData: function(){
105416         var me = this;
105417         Ext.applyIf(me.subTplData, {
105418             hiddenDataCls: me.hiddenDataCls
105419         });
105420         return me.callParent(arguments);
105421     },
105422
105423     afterRender: function(){
105424         var me = this;
105425         me.callParent(arguments);
105426         me.setHiddenValue(me.value);
105427     },
105428
105429     /**
105430      * @cfg {Ext.data.Store/Array} store
105431      * The data source to which this combo is bound. Acceptable values for this property are:
105432      *
105433      *   - **any {@link Ext.data.Store Store} subclass**
105434      *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
105435      *     {@link Ext.data.Field#name field names} to work with all data components.
105436      *
105437      *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)
105438      *
105439      *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
105440      *       {@link #valueField} and {@link #displayField})
105441      *
105442      *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
105443      *
105444      *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
105445      *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
105446      *
105447      * See also {@link #queryMode}.
105448      */
105449
105450     /**
105451      * @cfg {Boolean} multiSelect
105452      * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
105453      * items from the dropdown list. The combo's text field will show all selected values separated by the
105454      * {@link #delimiter}.
105455      */
105456     multiSelect: false,
105457
105458     /**
105459      * @cfg {String} delimiter
105460      * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
105461      * `{@link #multiSelect} = true`.
105462      */
105463     delimiter: ', ',
105464
105465     /**
105466      * @cfg {String} displayField
105467      * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
105468      *
105469      * See also `{@link #valueField}`.
105470      */
105471     displayField: 'text',
105472
105473     /**
105474      * @cfg {String} valueField (required)
105475      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
105476      * the value of the {@link #displayField} config).
105477      *
105478      * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
105479      * `{@link #displayField}`.
105480      */
105481
105482     /**
105483      * @cfg {String} triggerAction
105484      * The action to execute when the trigger is clicked.
105485      *
105486      *   - **`'all'`** :
105487      *
105488      *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
105489      *
105490      *   - **`'query'`** :
105491      *
105492      *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
105493      *
105494      * See also `{@link #queryParam}`.
105495      */
105496     triggerAction: 'all',
105497
105498     /**
105499      * @cfg {String} allQuery
105500      * The text query to send to the server to return all records for the list with no filtering
105501      */
105502     allQuery: '',
105503
105504     /**
105505      * @cfg {String} queryParam
105506      * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
105507      * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
105508      */
105509     queryParam: 'query',
105510
105511     /**
105512      * @cfg {String} queryMode
105513      * The mode in which the ComboBox uses the configured Store. Acceptable values are:
105514      *
105515      *   - **`'remote'`** :
105516      *
105517      *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
105518      *
105519      *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
105520      *     Ext.data.Store#load load}ed.
105521      *
105522      *     A parameter containing the typed string is sent in the load request. The default parameter name for the input
105523      *     string is `query`, but this can be configured using the {@link #queryParam} config.
105524      *
105525      *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
105526      *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load
105527      *     request which allows the server to further refine the returned dataset.
105528      *
105529      *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
105530      *     autocomplete.
105531      *
105532      *   - **`'local'`** :
105533      *
105534      *     ComboBox loads local data
105535      *
105536      *         var combo = new Ext.form.field.ComboBox({
105537      *             renderTo: document.body,
105538      *             queryMode: 'local',
105539      *             store: new Ext.data.ArrayStore({
105540      *                 id: 0,
105541      *                 fields: [
105542      *                     'myId',  // numeric value is the key
105543      *                     'displayText'
105544      *                 ],
105545      *                 data: [[1, 'item1'], [2, 'item2']]  // data is local
105546      *             }),
105547      *             valueField: 'myId',
105548      *             displayField: 'displayText',
105549      *             triggerAction: 'all'
105550      *         });
105551      */
105552     queryMode: 'remote',
105553
105554     queryCaching: true,
105555
105556     /**
105557      * @cfg {Number} pageSize
105558      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
105559      * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
105560      * parameters. Only applies when `{@link #queryMode} = 'remote'`.
105561      */
105562     pageSize: 0,
105563
105564     /**
105565      * @cfg {Number} queryDelay
105566      * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
105567      * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
105568      */
105569
105570     /**
105571      * @cfg {Number} minChars
105572      * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
105573      * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
105574      * `{@link Ext.form.field.Trigger#editable editable} = false`).
105575      */
105576
105577     /**
105578      * @cfg {Boolean} autoSelect
105579      * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
105580      * opened. A false value would cause nothing in the list to be highlighted automatically, so
105581      * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
105582      * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
105583      */
105584     autoSelect: true,
105585
105586     /**
105587      * @cfg {Boolean} typeAhead
105588      * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
105589      * ({@link #typeAheadDelay}) if it matches a known value.
105590      */
105591     typeAhead: false,
105592
105593     /**
105594      * @cfg {Number} typeAheadDelay
105595      * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
105596      */
105597     typeAheadDelay: 250,
105598
105599     /**
105600      * @cfg {Boolean} selectOnTab
105601      * Whether the Tab key should select the currently highlighted item.
105602      */
105603     selectOnTab: true,
105604
105605     /**
105606      * @cfg {Boolean} forceSelection
105607      * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
105608      * arbitrary text into the field.
105609      */
105610     forceSelection: false,
105611
105612     /**
105613      * @cfg {String} valueNotFoundText
105614      * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
105615      * be displayed as the field text if defined. If this default text is used, it means there
105616      * is no value set and no validation will occur on this field.
105617      */
105618
105619     /**
105620      * @property {String} lastQuery
105621      * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
105622      *
105623      *     var combo = new Ext.form.field.ComboBox({
105624      *         ...
105625      *         queryMode: 'remote',
105626      *         listeners: {
105627      *             // delete the previous query in the beforequery event or set
105628      *             // combo.lastQuery = null (this will reload the store the next time it expands)
105629      *             beforequery: function(qe){
105630      *                 delete qe.combo.lastQuery;
105631      *             }
105632      *         }
105633      *     });
105634      *
105635      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
105636      * combo with `lastQuery=''`. Example use:
105637      *
105638      *     var combo = new Ext.form.field.ComboBox({
105639      *         ...
105640      *         queryMode: 'local',
105641      *         triggerAction: 'all',
105642      *         lastQuery: ''
105643      *     });
105644      */
105645
105646     /**
105647      * @cfg {Object} defaultListConfig
105648      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
105649      */
105650     defaultListConfig: {
105651         emptyText: '',
105652         loadingText: 'Loading...',
105653         loadingHeight: 70,
105654         minWidth: 70,
105655         maxHeight: 300,
105656         shadow: 'sides'
105657     },
105658
105659     /**
105660      * @cfg {String/HTMLElement/Ext.Element} transform
105661      * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
105662      * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
105663      * will take precedence over this.
105664      */
105665
105666     /**
105667      * @cfg {Object} listConfig
105668      * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
105669      * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
105670      *
105671      *   - {@link Ext.view.BoundList#cls} - defaults to empty
105672      *   - {@link Ext.view.BoundList#emptyText} - defaults to empty string
105673      *   - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
105674      *   - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
105675      *   - {@link Ext.view.BoundList#minWidth} - defaults to `70`
105676      *   - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
105677      *   - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
105678      *   - {@link Ext.view.BoundList#resizable} - defaults to `false`
105679      *   - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
105680      *   - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
105681      *     field if {@link #matchFieldWidth} is true)
105682      */
105683
105684     //private
105685     ignoreSelection: 0,
105686
105687     initComponent: function() {
105688         var me = this,
105689             isDefined = Ext.isDefined,
105690             store = me.store,
105691             transform = me.transform,
105692             transformSelect, isLocalMode;
105693
105694         Ext.applyIf(me.renderSelectors, {
105695             hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
105696         });
105697         
105698         if (me.typeAhead && me.multiSelect) {
105699             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
105700         }
105701         if (me.typeAhead && !me.editable) {
105702             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
105703         }
105704         if (me.selectOnFocus && !me.editable) {
105705             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
105706         }
105707
105708         this.addEvents(
105709             /**
105710              * @event beforequery
105711              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
105712              * property to true.
105713              *
105714              * @param {Object} queryEvent An object that has these properties:
105715              *
105716              *   - `combo` : Ext.form.field.ComboBox
105717              *
105718              *     This combo box
105719              *
105720              *   - `query` : String
105721              *
105722              *     The query string
105723              *
105724              *   - `forceAll` : Boolean
105725              *
105726              *     True to force "all" query
105727              *
105728              *   - `cancel` : Boolean
105729              *
105730              *     Set to true to cancel the query
105731              */
105732             'beforequery',
105733
105734             /**
105735              * @event select
105736              * Fires when at least one list item is selected.
105737              * @param {Ext.form.field.ComboBox} combo This combo box
105738              * @param {Array} records The selected records
105739              */
105740             'select',
105741
105742             /**
105743              * @event beforeselect
105744              * Fires before the selected item is added to the collection
105745              * @param {Ext.form.field.ComboBox} combo This combo box
105746              * @param {Ext.data.Record} record The selected record
105747              * @param {Number} index The index of the selected record
105748              */
105749             'beforeselect',
105750
105751             /**
105752              * @event beforedeselect
105753              * Fires before the deselected item is removed from the collection
105754              * @param {Ext.form.field.ComboBox} combo This combo box
105755              * @param {Ext.data.Record} record The deselected record
105756              * @param {Number} index The index of the deselected record
105757              */
105758             'beforedeselect'
105759         );
105760
105761         // Build store from 'transform' HTML select element's options
105762         if (transform) {
105763             transformSelect = Ext.getDom(transform);
105764             if (transformSelect) {
105765                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
105766                     return [option.value, option.text];
105767                 });
105768                 if (!me.name) {
105769                     me.name = transformSelect.name;
105770                 }
105771                 if (!('value' in me)) {
105772                     me.value = transformSelect.value;
105773                 }
105774             }
105775         }
105776
105777         me.bindStore(store || 'ext-empty-store', true);
105778         store = me.store;
105779         if (store.autoCreated) {
105780             me.queryMode = 'local';
105781             me.valueField = me.displayField = 'field1';
105782             if (!store.expanded) {
105783                 me.displayField = 'field2';
105784             }
105785         }
105786
105787
105788         if (!isDefined(me.valueField)) {
105789             me.valueField = me.displayField;
105790         }
105791
105792         isLocalMode = me.queryMode === 'local';
105793         if (!isDefined(me.queryDelay)) {
105794             me.queryDelay = isLocalMode ? 10 : 500;
105795         }
105796         if (!isDefined(me.minChars)) {
105797             me.minChars = isLocalMode ? 0 : 4;
105798         }
105799
105800         if (!me.displayTpl) {
105801             me.displayTpl = Ext.create('Ext.XTemplate',
105802                 '<tpl for=".">' +
105803                     '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
105804                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
105805                 '</tpl>'
105806             );
105807         } else if (Ext.isString(me.displayTpl)) {
105808             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
105809         }
105810
105811         me.callParent();
105812
105813         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
105814
105815         // store has already been loaded, setValue
105816         if (me.store.getCount() > 0) {
105817             me.setValue(me.value);
105818         }
105819
105820         // render in place of 'transform' select
105821         if (transformSelect) {
105822             me.render(transformSelect.parentNode, transformSelect);
105823             Ext.removeNode(transformSelect);
105824             delete me.renderTo;
105825         }
105826     },
105827
105828     /**
105829      * Returns the store associated with this ComboBox.
105830      * @return {Ext.data.Store} The store
105831      */
105832     getStore : function(){
105833         return this.store;
105834     },
105835
105836     beforeBlur: function() {
105837         this.doQueryTask.cancel();
105838         this.assertValue();
105839     },
105840
105841     // private
105842     assertValue: function() {
105843         var me = this,
105844             value = me.getRawValue(),
105845             rec;
105846
105847         if (me.forceSelection) {
105848             if (me.multiSelect) {
105849                 // For multiselect, check that the current displayed value matches the current
105850                 // selection, if it does not then revert to the most recent selection.
105851                 if (value !== me.getDisplayValue()) {
105852                     me.setValue(me.lastSelection);
105853                 }
105854             } else {
105855                 // For single-select, match the displayed value to a record and select it,
105856                 // if it does not match a record then revert to the most recent selection.
105857                 rec = me.findRecordByDisplay(value);
105858                 if (rec) {
105859                     me.select(rec);
105860                 } else {
105861                     me.setValue(me.lastSelection);
105862                 }
105863             }
105864         }
105865         me.collapse();
105866     },
105867
105868     onTypeAhead: function() {
105869         var me = this,
105870             displayField = me.displayField,
105871             record = me.store.findRecord(displayField, me.getRawValue()),
105872             boundList = me.getPicker(),
105873             newValue, len, selStart;
105874
105875         if (record) {
105876             newValue = record.get(displayField);
105877             len = newValue.length;
105878             selStart = me.getRawValue().length;
105879
105880             boundList.highlightItem(boundList.getNode(record));
105881
105882             if (selStart !== 0 && selStart !== len) {
105883                 me.setRawValue(newValue);
105884                 me.selectText(selStart, newValue.length);
105885             }
105886         }
105887     },
105888
105889     // invoked when a different store is bound to this combo
105890     // than the original
105891     resetToDefault: function() {
105892
105893     },
105894
105895     bindStore: function(store, initial) {
105896         var me = this,
105897             oldStore = me.store;
105898
105899         // this code directly accesses this.picker, bc invoking getPicker
105900         // would create it when we may be preping to destroy it
105901         if (oldStore && !initial) {
105902             if (oldStore !== store && oldStore.autoDestroy) {
105903                 oldStore.destroyStore();
105904             } else {
105905                 oldStore.un({
105906                     scope: me,
105907                     load: me.onLoad,
105908                     exception: me.collapse
105909                 });
105910             }
105911             if (!store) {
105912                 me.store = null;
105913                 if (me.picker) {
105914                     me.picker.bindStore(null);
105915                 }
105916             }
105917         }
105918         if (store) {
105919             if (!initial) {
105920                 me.resetToDefault();
105921             }
105922
105923             me.store = Ext.data.StoreManager.lookup(store);
105924             me.store.on({
105925                 scope: me,
105926                 load: me.onLoad,
105927                 exception: me.collapse
105928             });
105929
105930             if (me.picker) {
105931                 me.picker.bindStore(store);
105932             }
105933         }
105934     },
105935
105936     onLoad: function() {
105937         var me = this,
105938             value = me.value;
105939
105940         // If performing a remote query upon the raw value...
105941         if (me.rawQuery) {
105942             me.rawQuery = false;
105943             me.syncSelection();
105944             if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
105945                 me.doAutoSelect();
105946             }
105947         }
105948         // If store initial load or triggerAction: 'all' trigger click.
105949         else {
105950             // Set the value on load
105951             if (me.value) {
105952                 me.setValue(me.value);
105953             } else {
105954                 // There's no value.
105955                 // Highlight the first item in the list if autoSelect: true
105956                 if (me.store.getCount()) {
105957                     me.doAutoSelect();
105958                 } else {
105959                     me.setValue('');
105960                 }
105961             }
105962         }
105963     },
105964
105965     /**
105966      * @private
105967      * Execute the query with the raw contents within the textfield.
105968      */
105969     doRawQuery: function() {
105970         this.doQuery(this.getRawValue(), false, true);
105971     },
105972
105973     /**
105974      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
105975      * allowing the query action to be canceled if needed.
105976      *
105977      * @param {String} queryString The SQL query to execute
105978      * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
105979      * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
105980      * previously saved in the current store.
105981      * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
105982      * resulting store load to leave the raw value undisturbed.
105983      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
105984      * handler.
105985      */
105986     doQuery: function(queryString, forceAll, rawQuery) {
105987         queryString = queryString || '';
105988
105989         // store in object and pass by reference in 'beforequery'
105990         // so that client code can modify values.
105991         var me = this,
105992             qe = {
105993                 query: queryString,
105994                 forceAll: forceAll,
105995                 combo: me,
105996                 cancel: false
105997             },
105998             store = me.store,
105999             isLocalMode = me.queryMode === 'local';
106000
106001         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
106002             return false;
106003         }
106004
106005         // get back out possibly modified values
106006         queryString = qe.query;
106007         forceAll = qe.forceAll;
106008
106009         // query permitted to run
106010         if (forceAll || (queryString.length >= me.minChars)) {
106011             // expand before starting query so LoadMask can position itself correctly
106012             me.expand();
106013
106014             // make sure they aren't querying the same thing
106015             if (!me.queryCaching || me.lastQuery !== queryString) {
106016                 me.lastQuery = queryString;
106017
106018                 if (isLocalMode) {
106019                     // forceAll means no filtering - show whole dataset.
106020                     if (forceAll) {
106021                         store.clearFilter();
106022                     } else {
106023                         // Clear filter, but supress event so that the BoundList is not immediately updated.
106024                         store.clearFilter(true);
106025                         store.filter(me.displayField, queryString);
106026                     }
106027                 } else {
106028                     // Set flag for onLoad handling to know how the Store was loaded
106029                     me.rawQuery = rawQuery;
106030
106031                     // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
106032                     // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
106033                     if (me.pageSize) {
106034                         // if we're paging, we've changed the query so start at page 1.
106035                         me.loadPage(1);
106036                     } else {
106037                         store.load({
106038                             params: me.getParams(queryString)
106039                         });
106040                     }
106041                 }
106042             }
106043
106044             // Clear current selection if it does not match the current value in the field
106045             if (me.getRawValue() !== me.getDisplayValue()) {
106046                 me.ignoreSelection++;
106047                 me.picker.getSelectionModel().deselectAll();
106048                 me.ignoreSelection--;
106049             }
106050
106051             if (isLocalMode) {
106052                 me.doAutoSelect();
106053             }
106054             if (me.typeAhead) {
106055                 me.doTypeAhead();
106056             }
106057         }
106058         return true;
106059     },
106060
106061     loadPage: function(pageNum){
106062         this.store.loadPage(pageNum, {
106063             params: this.getParams(this.lastQuery)
106064         });
106065     },
106066
106067     onPageChange: function(toolbar, newPage){
106068         /*
106069          * Return false here so we can call load ourselves and inject the query param.
106070          * We don't want to do this for every store load since the developer may load
106071          * the store through some other means so we won't add the query param.
106072          */
106073         this.loadPage(newPage);
106074         return false;
106075     },
106076
106077     // private
106078     getParams: function(queryString) {
106079         var params = {},
106080             param = this.queryParam;
106081
106082         if (param) {
106083             params[param] = queryString;
106084         }
106085         return params;
106086     },
106087
106088     /**
106089      * @private
106090      * If the autoSelect config is true, and the picker is open, highlights the first item.
106091      */
106092     doAutoSelect: function() {
106093         var me = this,
106094             picker = me.picker,
106095             lastSelected, itemNode;
106096         if (picker && me.autoSelect && me.store.getCount() > 0) {
106097             // Highlight the last selected item and scroll it into view
106098             lastSelected = picker.getSelectionModel().lastSelected;
106099             itemNode = picker.getNode(lastSelected || 0);
106100             if (itemNode) {
106101                 picker.highlightItem(itemNode);
106102                 picker.listEl.scrollChildIntoView(itemNode, false);
106103             }
106104         }
106105     },
106106
106107     doTypeAhead: function() {
106108         if (!this.typeAheadTask) {
106109             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
106110         }
106111         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
106112             this.typeAheadTask.delay(this.typeAheadDelay);
106113         }
106114     },
106115
106116     onTriggerClick: function() {
106117         var me = this;
106118         if (!me.readOnly && !me.disabled) {
106119             if (me.isExpanded) {
106120                 me.collapse();
106121             } else {
106122                 me.onFocus({});
106123                 if (me.triggerAction === 'all') {
106124                     me.doQuery(me.allQuery, true);
106125                 } else {
106126                     me.doQuery(me.getRawValue(), false, true);
106127                 }
106128             }
106129             me.inputEl.focus();
106130         }
106131     },
106132
106133
106134     // store the last key and doQuery if relevant
106135     onKeyUp: function(e, t) {
106136         var me = this,
106137             key = e.getKey();
106138
106139         if (!me.readOnly && !me.disabled && me.editable) {
106140             me.lastKey = key;
106141             // we put this in a task so that we can cancel it if a user is
106142             // in and out before the queryDelay elapses
106143
106144             // perform query w/ any normal key or backspace or delete
106145             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
106146                 me.doQueryTask.delay(me.queryDelay);
106147             }
106148         }
106149
106150         if (me.enableKeyEvents) {
106151             me.callParent(arguments);
106152         }
106153     },
106154
106155     initEvents: function() {
106156         var me = this;
106157         me.callParent();
106158
106159         /*
106160          * Setup keyboard handling. If enableKeyEvents is true, we already have
106161          * a listener on the inputEl for keyup, so don't create a second.
106162          */
106163         if (!me.enableKeyEvents) {
106164             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
106165         }
106166     },
106167     
106168     onDestroy: function(){
106169         this.bindStore(null);
106170         this.callParent();    
106171     },
106172
106173     createPicker: function() {
106174         var me = this,
106175             picker,
106176             menuCls = Ext.baseCSSPrefix + 'menu',
106177             opts = Ext.apply({
106178                 pickerField: me,
106179                 selModel: {
106180                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
106181                 },
106182                 floating: true,
106183                 hidden: true,
106184                 ownerCt: me.ownerCt,
106185                 cls: me.el.up('.' + menuCls) ? menuCls : '',
106186                 store: me.store,
106187                 displayField: me.displayField,
106188                 focusOnToFront: false,
106189                 pageSize: me.pageSize,
106190                 tpl: me.tpl
106191             }, me.listConfig, me.defaultListConfig);
106192
106193         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
106194         if (me.pageSize) {
106195             picker.pagingToolbar.on('beforechange', me.onPageChange, me);
106196         }
106197
106198         me.mon(picker, {
106199             itemclick: me.onItemClick,
106200             refresh: me.onListRefresh,
106201             scope: me
106202         });
106203
106204         me.mon(picker.getSelectionModel(), {
106205             'beforeselect': me.onBeforeSelect,
106206             'beforedeselect': me.onBeforeDeselect,
106207             'selectionchange': me.onListSelectionChange,
106208             scope: me
106209         });
106210
106211         return picker;
106212     },
106213
106214     alignPicker: function(){
106215         var me = this,
106216             picker = me.picker,
106217             heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
106218             heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
106219             space = Math.max(heightAbove, heightBelow);
106220
106221         me.callParent();
106222         if (picker.getHeight() > space) {
106223             picker.setHeight(space - 5); // have some leeway so we aren't flush against
106224             me.doAlign();
106225         }
106226     },
106227
106228     onListRefresh: function() {
106229         this.alignPicker();
106230         this.syncSelection();
106231     },
106232
106233     onItemClick: function(picker, record){
106234         /*
106235          * If we're doing single selection, the selection change events won't fire when
106236          * clicking on the selected element. Detect it here.
106237          */
106238         var me = this,
106239             lastSelection = me.lastSelection,
106240             valueField = me.valueField,
106241             selected;
106242
106243         if (!me.multiSelect && lastSelection) {
106244             selected = lastSelection[0];
106245             if (selected && (record.get(valueField) === selected.get(valueField))) {
106246                 // Make sure we also update the display value if it's only partial
106247                 me.displayTplData = [record.data];
106248                 me.setRawValue(me.getDisplayValue());
106249                 me.collapse();
106250             }
106251         }
106252     },
106253
106254     onBeforeSelect: function(list, record) {
106255         return this.fireEvent('beforeselect', this, record, record.index);
106256     },
106257
106258     onBeforeDeselect: function(list, record) {
106259         return this.fireEvent('beforedeselect', this, record, record.index);
106260     },
106261
106262     onListSelectionChange: function(list, selectedRecords) {
106263         var me = this,
106264             isMulti = me.multiSelect,
106265             hasRecords = selectedRecords.length > 0;
106266         // Only react to selection if it is not called from setValue, and if our list is
106267         // expanded (ignores changes to the selection model triggered elsewhere)
106268         if (!me.ignoreSelection && me.isExpanded) {
106269             if (!isMulti) {
106270                 Ext.defer(me.collapse, 1, me);
106271             }
106272             /*
106273              * Only set the value here if we're in multi selection mode or we have
106274              * a selection. Otherwise setValue will be called with an empty value
106275              * which will cause the change event to fire twice.
106276              */
106277             if (isMulti || hasRecords) {
106278                 me.setValue(selectedRecords, false);
106279             }
106280             if (hasRecords) {
106281                 me.fireEvent('select', me, selectedRecords);
106282             }
106283             me.inputEl.focus();
106284         }
106285     },
106286
106287     /**
106288      * @private
106289      * Enables the key nav for the BoundList when it is expanded.
106290      */
106291     onExpand: function() {
106292         var me = this,
106293             keyNav = me.listKeyNav,
106294             selectOnTab = me.selectOnTab,
106295             picker = me.getPicker();
106296
106297         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
106298         if (keyNav) {
106299             keyNav.enable();
106300         } else {
106301             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
106302                 boundList: picker,
106303                 forceKeyDown: true,
106304                 tab: function(e) {
106305                     if (selectOnTab) {
106306                         this.selectHighlighted(e);
106307                         me.triggerBlur();
106308                     }
106309                     // Tab key event is allowed to propagate to field
106310                     return true;
106311                 }
106312             });
106313         }
106314
106315         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
106316         if (selectOnTab) {
106317             me.ignoreMonitorTab = true;
106318         }
106319
106320         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
106321         me.inputEl.focus();
106322     },
106323
106324     /**
106325      * @private
106326      * Disables the key nav for the BoundList when it is collapsed.
106327      */
106328     onCollapse: function() {
106329         var me = this,
106330             keyNav = me.listKeyNav;
106331         if (keyNav) {
106332             keyNav.disable();
106333             me.ignoreMonitorTab = false;
106334         }
106335     },
106336
106337     /**
106338      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
106339      * @param {Object} r
106340      */
106341     select: function(r) {
106342         this.setValue(r, true);
106343     },
106344
106345     /**
106346      * Finds the record by searching for a specific field/value combination.
106347      * @param {String} field The name of the field to test.
106348      * @param {Object} value The value to match the field against.
106349      * @return {Ext.data.Model} The matched record or false.
106350      */
106351     findRecord: function(field, value) {
106352         var ds = this.store,
106353             idx = ds.findExact(field, value);
106354         return idx !== -1 ? ds.getAt(idx) : false;
106355     },
106356
106357     /**
106358      * Finds the record by searching values in the {@link #valueField}.
106359      * @param {Object} value The value to match the field against.
106360      * @return {Ext.data.Model} The matched record or false.
106361      */
106362     findRecordByValue: function(value) {
106363         return this.findRecord(this.valueField, value);
106364     },
106365
106366     /**
106367      * Finds the record by searching values in the {@link #displayField}.
106368      * @param {Object} value The value to match the field against.
106369      * @return {Ext.data.Model} The matched record or false.
106370      */
106371     findRecordByDisplay: function(value) {
106372         return this.findRecord(this.displayField, value);
106373     },
106374
106375     /**
106376      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
106377      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
106378      * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
106379      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
106380      * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
106381      * or an Array of Strings or Models.
106382      * @return {Ext.form.field.Field} this
106383      */
106384     setValue: function(value, doSelect) {
106385         var me = this,
106386             valueNotFoundText = me.valueNotFoundText,
106387             inputEl = me.inputEl,
106388             i, len, record,
106389             models = [],
106390             displayTplData = [],
106391             processedValue = [];
106392
106393         if (me.store.loading) {
106394             // Called while the Store is loading. Ensure it is processed by the onLoad method.
106395             me.value = value;
106396             me.setHiddenValue(me.value);
106397             return me;
106398         }
106399
106400         // This method processes multi-values, so ensure value is an array.
106401         value = Ext.Array.from(value);
106402
106403         // Loop through values
106404         for (i = 0, len = value.length; i < len; i++) {
106405             record = value[i];
106406             if (!record || !record.isModel) {
106407                 record = me.findRecordByValue(record);
106408             }
106409             // record found, select it.
106410             if (record) {
106411                 models.push(record);
106412                 displayTplData.push(record.data);
106413                 processedValue.push(record.get(me.valueField));
106414             }
106415             // record was not found, this could happen because
106416             // store is not loaded or they set a value not in the store
106417             else {
106418                 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
106419                 if (!me.forceSelection) {
106420                     displayTplData.push(value[i]);
106421                     processedValue.push(value[i]);
106422                 }
106423                 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
106424                 else if (Ext.isDefined(valueNotFoundText)) {
106425                     displayTplData.push(valueNotFoundText);
106426                 }
106427             }
106428         }
106429
106430         // Set the value of this field. If we are multiselecting, then that is an array.
106431         me.setHiddenValue(processedValue);
106432         me.value = me.multiSelect ? processedValue : processedValue[0];
106433         if (!Ext.isDefined(me.value)) {
106434             me.value = null;
106435         }
106436         me.displayTplData = displayTplData; //store for getDisplayValue method
106437         me.lastSelection = me.valueModels = models;
106438
106439         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
106440             inputEl.removeCls(me.emptyCls);
106441         }
106442
106443         // Calculate raw value from the collection of Model data
106444         me.setRawValue(me.getDisplayValue());
106445         me.checkChange();
106446
106447         if (doSelect !== false) {
106448             me.syncSelection();
106449         }
106450         me.applyEmptyText();
106451
106452         return me;
106453     },
106454
106455     /**
106456      * @private
106457      * Set the value of {@link #hiddenDataEl}
106458      * Dynamically adds and removes input[type=hidden] elements
106459      */
106460     setHiddenValue: function(values){
106461         var me = this, i;
106462         if (!me.hiddenDataEl) {
106463             return;
106464         }
106465         values = Ext.Array.from(values);
106466         var dom = me.hiddenDataEl.dom,
106467             childNodes = dom.childNodes,
106468             input = childNodes[0],
106469             valueCount = values.length,
106470             childrenCount = childNodes.length;
106471         
106472         if (!input && valueCount > 0) {
106473             me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
106474             childrenCount = 1;
106475             input = dom.firstChild;
106476         }
106477         while (childrenCount > valueCount) {
106478             dom.removeChild(childNodes[0]);
106479             -- childrenCount;
106480         }
106481         while (childrenCount < valueCount) {
106482             dom.appendChild(input.cloneNode(true));
106483             ++ childrenCount;
106484         }
106485         for (i = 0; i < valueCount; i++) {
106486             childNodes[i].value = values[i];
106487         }
106488     },
106489
106490     /**
106491      * @private Generates the string value to be displayed in the text field for the currently stored value
106492      */
106493     getDisplayValue: function() {
106494         return this.displayTpl.apply(this.displayTplData);
106495     },
106496
106497     getValue: function() {
106498         // If the user has not changed the raw field value since a value was selected from the list,
106499         // then return the structured value from the selection. If the raw field value is different
106500         // than what would be displayed due to selection, return that raw value.
106501         var me = this,
106502             picker = me.picker,
106503             rawValue = me.getRawValue(), //current value of text field
106504             value = me.value; //stored value from last selection or setValue() call
106505
106506         if (me.getDisplayValue() !== rawValue) {
106507             value = rawValue;
106508             me.value = me.displayTplData = me.valueModels = null;
106509             if (picker) {
106510                 me.ignoreSelection++;
106511                 picker.getSelectionModel().deselectAll();
106512                 me.ignoreSelection--;
106513             }
106514         }
106515
106516         return value;
106517     },
106518
106519     getSubmitValue: function() {
106520         return this.getValue();
106521     },
106522
106523     isEqual: function(v1, v2) {
106524         var fromArray = Ext.Array.from,
106525             i, len;
106526
106527         v1 = fromArray(v1);
106528         v2 = fromArray(v2);
106529         len = v1.length;
106530
106531         if (len !== v2.length) {
106532             return false;
106533         }
106534
106535         for(i = 0; i < len; i++) {
106536             if (v2[i] !== v1[i]) {
106537                 return false;
106538             }
106539         }
106540
106541         return true;
106542     },
106543
106544     /**
106545      * Clears any value currently set in the ComboBox.
106546      */
106547     clearValue: function() {
106548         this.setValue([]);
106549     },
106550
106551     /**
106552      * @private Synchronizes the selection in the picker to match the current value of the combobox.
106553      */
106554     syncSelection: function() {
106555         var me = this,
106556             ExtArray = Ext.Array,
106557             picker = me.picker,
106558             selection, selModel;
106559         if (picker) {
106560             // From the value, find the Models that are in the store's current data
106561             selection = [];
106562             ExtArray.forEach(me.valueModels || [], function(value) {
106563                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
106564                     selection.push(value);
106565                 }
106566             });
106567
106568             // Update the selection to match
106569             me.ignoreSelection++;
106570             selModel = picker.getSelectionModel();
106571             selModel.deselectAll();
106572             if (selection.length) {
106573                 selModel.select(selection);
106574             }
106575             me.ignoreSelection--;
106576         }
106577     }
106578 });
106579
106580 /**
106581  * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
106582  * to allow browsing and selection of year/months combinations.
106583  */
106584 Ext.define('Ext.picker.Month', {
106585     extend: 'Ext.Component',
106586     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
106587     alias: 'widget.monthpicker',
106588     alternateClassName: 'Ext.MonthPicker',
106589
106590     renderTpl: [
106591         '<div id="{id}-bodyEl" class="{baseCls}-body">',
106592           '<div class="{baseCls}-months">',
106593               '<tpl for="months">',
106594                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
106595               '</tpl>',
106596           '</div>',
106597           '<div class="{baseCls}-years">',
106598               '<div class="{baseCls}-yearnav">',
106599                   '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
106600                   '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
106601               '</div>',
106602               '<tpl for="years">',
106603                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
106604               '</tpl>',
106605           '</div>',
106606           '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
106607         '</div>',
106608         '<tpl if="showButtons">',
106609           '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>',
106610         '</tpl>'
106611     ],
106612
106613     /**
106614      * @cfg {String} okText The text to display on the ok button.
106615      */
106616     okText: 'OK',
106617
106618     /**
106619      * @cfg {String} cancelText The text to display on the cancel button.
106620      */
106621     cancelText: 'Cancel',
106622
106623     /**
106624      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
106625      */
106626     baseCls: Ext.baseCSSPrefix + 'monthpicker',
106627
106628     /**
106629      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
106630      */
106631     showButtons: true,
106632
106633     /**
106634      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
106635      * <tt>'x-monthpicker-selected'</tt>
106636      */
106637
106638     /**
106639      * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
106640      */
106641     width: 178,
106642
106643     // used when attached to date picker which isnt showing buttons
106644     smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
106645
106646     // private
106647     totalYears: 10,
106648     yearOffset: 5, // 10 years in total, 2 per row
106649     monthOffset: 6, // 12 months, 2 per row
106650
106651     // private, inherit docs
106652     initComponent: function(){
106653         var me = this;
106654
106655         me.selectedCls = me.baseCls + '-selected';
106656         me.addEvents(
106657             /**
106658              * @event cancelclick
106659              * Fires when the cancel button is pressed.
106660              * @param {Ext.picker.Month} this
106661              */
106662             'cancelclick',
106663
106664             /**
106665              * @event monthclick
106666              * Fires when a month is clicked.
106667              * @param {Ext.picker.Month} this
106668              * @param {Array} value The current value
106669              */
106670             'monthclick',
106671
106672             /**
106673              * @event monthdblclick
106674              * Fires when a month is clicked.
106675              * @param {Ext.picker.Month} this
106676              * @param {Array} value The current value
106677              */
106678             'monthdblclick',
106679
106680             /**
106681              * @event okclick
106682              * Fires when the ok button is pressed.
106683              * @param {Ext.picker.Month} this
106684              * @param {Array} value The current value
106685              */
106686             'okclick',
106687
106688             /**
106689              * @event select
106690              * Fires when a month/year is selected.
106691              * @param {Ext.picker.Month} this
106692              * @param {Array} value The current value
106693              */
106694             'select',
106695
106696             /**
106697              * @event yearclick
106698              * Fires when a year is clicked.
106699              * @param {Ext.picker.Month} this
106700              * @param {Array} value The current value
106701              */
106702             'yearclick',
106703
106704             /**
106705              * @event yeardblclick
106706              * Fires when a year is clicked.
106707              * @param {Ext.picker.Month} this
106708              * @param {Array} value The current value
106709              */
106710             'yeardblclick'
106711         );
106712         if (me.small) {
106713             me.addCls(me.smallCls);
106714         }
106715         me.setValue(me.value);
106716         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
106717         this.callParent();
106718     },
106719
106720     // private, inherit docs
106721     onRender: function(ct, position){
106722         var me = this,
106723             i = 0,
106724             months = [],
106725             shortName = Ext.Date.getShortMonthName,
106726             monthLen = me.monthOffset;
106727
106728         for (; i < monthLen; ++i) {
106729             months.push(shortName(i), shortName(i + monthLen));
106730         }
106731
106732         Ext.apply(me.renderData, {
106733             months: months,
106734             years: me.getYears(),
106735             showButtons: me.showButtons
106736         });
106737
106738         me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl');
106739
106740         me.callParent(arguments);
106741     },
106742
106743     // private, inherit docs
106744     afterRender: function(){
106745         var me = this,
106746             body = me.bodyEl,
106747             buttonsEl = me.buttonsEl;
106748
106749         me.callParent();
106750
106751         me.mon(body, 'click', me.onBodyClick, me);
106752         me.mon(body, 'dblclick', me.onBodyClick, me);
106753
106754         // keep a reference to the year/month elements since we'll be re-using them
106755         me.years = body.select('.' + me.baseCls + '-year a');
106756         me.months = body.select('.' + me.baseCls + '-month a');
106757
106758         if (me.showButtons) {
106759             me.okBtn = Ext.create('Ext.button.Button', {
106760                 text: me.okText,
106761                 renderTo: buttonsEl,
106762                 handler: me.onOkClick,
106763                 scope: me
106764             });
106765             me.cancelBtn = Ext.create('Ext.button.Button', {
106766                 text: me.cancelText,
106767                 renderTo: buttonsEl,
106768                 handler: me.onCancelClick,
106769                 scope: me
106770             });
106771         }
106772
106773         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106774             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
106775         });
106776
106777         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
106778         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106779             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
106780         });
106781         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
106782         me.updateBody();
106783     },
106784
106785     /**
106786      * Set the value for the picker.
106787      * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
106788      * it can be an array, with the month as the first index and the year as the second.
106789      * @return {Ext.picker.Month} this
106790      */
106791     setValue: function(value){
106792         var me = this,
106793             active = me.activeYear,
106794             offset = me.monthOffset,
106795             year,
106796             index;
106797
106798         if (!value) {
106799             me.value = [null, null];
106800         } else if (Ext.isDate(value)) {
106801             me.value = [value.getMonth(), value.getFullYear()];
106802         } else {
106803             me.value = [value[0], value[1]];
106804         }
106805
106806         if (me.rendered) {
106807             year = me.value[1];
106808             if (year !== null) {
106809                 if ((year < active || year > active + me.yearOffset)) {
106810                     me.activeYear = year - me.yearOffset + 1;
106811                 }
106812             }
106813             me.updateBody();
106814         }
106815
106816         return me;
106817     },
106818
106819     /**
106820      * Gets the selected value. It is returned as an array [month, year]. It may
106821      * be a partial value, for example [null, 2010]. The month is returned as
106822      * 0 based.
106823      * @return {Number[]} The selected value
106824      */
106825     getValue: function(){
106826         return this.value;
106827     },
106828
106829     /**
106830      * Checks whether the picker has a selection
106831      * @return {Boolean} Returns true if both a month and year have been selected
106832      */
106833     hasSelection: function(){
106834         var value = this.value;
106835         return value[0] !== null && value[1] !== null;
106836     },
106837
106838     /**
106839      * Get an array of years to be pushed in the template. It is not in strict
106840      * numerical order because we want to show them in columns.
106841      * @private
106842      * @return {Number[]} An array of years
106843      */
106844     getYears: function(){
106845         var me = this,
106846             offset = me.yearOffset,
106847             start = me.activeYear, // put the "active" year on the left
106848             end = start + offset,
106849             i = start,
106850             years = [];
106851
106852         for (; i < end; ++i) {
106853             years.push(i, i + offset);
106854         }
106855
106856         return years;
106857     },
106858
106859     /**
106860      * Update the years in the body based on any change
106861      * @private
106862      */
106863     updateBody: function(){
106864         var me = this,
106865             years = me.years,
106866             months = me.months,
106867             yearNumbers = me.getYears(),
106868             cls = me.selectedCls,
106869             value = me.getYear(null),
106870             month = me.value[0],
106871             monthOffset = me.monthOffset,
106872             year;
106873
106874         if (me.rendered) {
106875             years.removeCls(cls);
106876             months.removeCls(cls);
106877             years.each(function(el, all, index){
106878                 year = yearNumbers[index];
106879                 el.dom.innerHTML = year;
106880                 if (year == value) {
106881                     el.dom.className = cls;
106882                 }
106883             });
106884             if (month !== null) {
106885                 if (month < monthOffset) {
106886                     month = month * 2;
106887                 } else {
106888                     month = (month - monthOffset) * 2 + 1;
106889                 }
106890                 months.item(month).addCls(cls);
106891             }
106892         }
106893     },
106894
106895     /**
106896      * Gets the current year value, or the default.
106897      * @private
106898      * @param {Number} defaultValue The default value to use if the year is not defined.
106899      * @param {Number} offset A number to offset the value by
106900      * @return {Number} The year value
106901      */
106902     getYear: function(defaultValue, offset) {
106903         var year = this.value[1];
106904         offset = offset || 0;
106905         return year === null ? defaultValue : year + offset;
106906     },
106907
106908     /**
106909      * React to clicks on the body
106910      * @private
106911      */
106912     onBodyClick: function(e, t) {
106913         var me = this,
106914             isDouble = e.type == 'dblclick';
106915
106916         if (e.getTarget('.' + me.baseCls + '-month')) {
106917             e.stopEvent();
106918             me.onMonthClick(t, isDouble);
106919         } else if (e.getTarget('.' + me.baseCls + '-year')) {
106920             e.stopEvent();
106921             me.onYearClick(t, isDouble);
106922         }
106923     },
106924
106925     /**
106926      * Modify the year display by passing an offset.
106927      * @param {Number} [offset=10] The offset to move by.
106928      */
106929     adjustYear: function(offset){
106930         if (typeof offset != 'number') {
106931             offset = this.totalYears;
106932         }
106933         this.activeYear += offset;
106934         this.updateBody();
106935     },
106936
106937     /**
106938      * React to the ok button being pressed
106939      * @private
106940      */
106941     onOkClick: function(){
106942         this.fireEvent('okclick', this, this.value);
106943     },
106944
106945     /**
106946      * React to the cancel button being pressed
106947      * @private
106948      */
106949     onCancelClick: function(){
106950         this.fireEvent('cancelclick', this);
106951     },
106952
106953     /**
106954      * React to a month being clicked
106955      * @private
106956      * @param {HTMLElement} target The element that was clicked
106957      * @param {Boolean} isDouble True if the event was a doubleclick
106958      */
106959     onMonthClick: function(target, isDouble){
106960         var me = this;
106961         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
106962         me.updateBody();
106963         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106964         me.fireEvent('select', me, me.value);
106965     },
106966
106967     /**
106968      * React to a year being clicked
106969      * @private
106970      * @param {HTMLElement} target The element that was clicked
106971      * @param {Boolean} isDouble True if the event was a doubleclick
106972      */
106973     onYearClick: function(target, isDouble){
106974         var me = this;
106975         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
106976         me.updateBody();
106977         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106978         me.fireEvent('select', me, me.value);
106979
106980     },
106981
106982     /**
106983      * Returns an offsetted number based on the position in the collection. Since our collections aren't
106984      * numerically ordered, this function helps to normalize those differences.
106985      * @private
106986      * @param {Object} index
106987      * @param {Object} offset
106988      * @return {Number} The correctly offsetted number
106989      */
106990     resolveOffset: function(index, offset){
106991         if (index % 2 === 0) {
106992             return (index / 2);
106993         } else {
106994             return offset + Math.floor(index / 2);
106995         }
106996     },
106997
106998     // private, inherit docs
106999     beforeDestroy: function(){
107000         var me = this;
107001         me.years = me.months = null;
107002         Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
107003         me.callParent();
107004     }
107005 });
107006
107007 /**
107008  * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
107009  * dates in a popup next to the field, but may also be used with other components.
107010  *
107011  * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
107012  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
107013  *
107014  * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
107015  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
107016  *
107017  * All the string values documented below may be overridden by including an Ext locale file in your page.
107018  *
107019  *     @example
107020  *     Ext.create('Ext.panel.Panel', {
107021  *         title: 'Choose a future date:',
107022  *         width: 200,
107023  *         bodyPadding: 10,
107024  *         renderTo: Ext.getBody(),
107025  *         items: [{
107026  *             xtype: 'datepicker',
107027  *             minDate: new Date(),
107028  *             handler: function(picker, date) {
107029  *                 // do something with the selected date
107030  *             }
107031  *         }]
107032  *     });
107033  */
107034 Ext.define('Ext.picker.Date', {
107035     extend: 'Ext.Component',
107036     requires: [
107037         'Ext.XTemplate',
107038         'Ext.button.Button',
107039         'Ext.button.Split',
107040         'Ext.util.ClickRepeater',
107041         'Ext.util.KeyNav',
107042         'Ext.EventObject',
107043         'Ext.fx.Manager',
107044         'Ext.picker.Month'
107045     ],
107046     alias: 'widget.datepicker',
107047     alternateClassName: 'Ext.DatePicker',
107048
107049     renderTpl: [
107050         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
107051             '<div role="presentation" class="{baseCls}-header">',
107052                 '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
107053                 '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
107054                 '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
107055             '</div>',
107056             '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
107057                 '<thead role="presentation"><tr role="presentation">',
107058                     '<tpl for="dayNames">',
107059                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
107060                     '</tpl>',
107061                 '</tr></thead>',
107062                 '<tbody role="presentation"><tr role="presentation">',
107063                     '<tpl for="days">',
107064                         '{#:this.isEndOfWeek}',
107065                         '<td role="gridcell" id="{[Ext.id()]}">',
107066                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
107067                                 '<em role="presentation"><span role="presentation"></span></em>',
107068                             '</a>',
107069                         '</td>',
107070                     '</tpl>',
107071                 '</tr></tbody>',
107072             '</table>',
107073             '<tpl if="showToday">',
107074                 '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
107075             '</tpl>',
107076         '</div>',
107077         {
107078             firstInitial: function(value) {
107079                 return value.substr(0,1);
107080             },
107081             isEndOfWeek: function(value) {
107082                 // convert from 1 based index to 0 based
107083                 // by decrementing value once.
107084                 value--;
107085                 var end = value % 7 === 0 && value !== 0;
107086                 return end ? '</tr><tr role="row">' : '';
107087             },
107088             longDay: function(value){
107089                 return Ext.Date.format(value, this.longDayFormat);
107090             }
107091         }
107092     ],
107093
107094     ariaTitle: 'Date Picker',
107095
107096     /**
107097      * @cfg {String} todayText
107098      * The text to display on the button that selects the current date
107099      */
107100     todayText : 'Today',
107101
107102     /**
107103      * @cfg {Function} handler
107104      * Optional. A function that will handle the select event of this picker. The handler is passed the following
107105      * parameters:
107106      *
107107      *   - `picker` : Ext.picker.Date
107108      *
107109      * This Date picker.
107110      *
107111      *   - `date` : Date
107112      *
107113      * The selected date.
107114      */
107115
107116     /**
107117      * @cfg {Object} scope
107118      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
107119      * DatePicker instance.
107120      */
107121
107122     /**
107123      * @cfg {String} todayTip
107124      * A string used to format the message for displaying in a tooltip over the button that selects the current date.
107125      * The `{0}` token in string is replaced by today's date.
107126      */
107127     todayTip : '{0} (Spacebar)',
107128
107129     /**
107130      * @cfg {String} minText
107131      * The error text to display if the minDate validation fails.
107132      */
107133     minText : 'This date is before the minimum date',
107134
107135     /**
107136      * @cfg {String} maxText
107137      * The error text to display if the maxDate validation fails.
107138      */
107139     maxText : 'This date is after the maximum date',
107140
107141     /**
107142      * @cfg {String} format
107143      * The default date format string which can be overriden for localization support. The format must be valid
107144      * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
107145      */
107146
107147     /**
107148      * @cfg {String} disabledDaysText
107149      * The tooltip to display when the date falls on a disabled day.
107150      */
107151     disabledDaysText : 'Disabled',
107152
107153     /**
107154      * @cfg {String} disabledDatesText
107155      * The tooltip text to display when the date falls on a disabled date.
107156      */
107157     disabledDatesText : 'Disabled',
107158
107159     /**
107160      * @cfg {String[]} monthNames
107161      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
107162      */
107163
107164     /**
107165      * @cfg {String[]} dayNames
107166      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
107167      */
107168
107169     /**
107170      * @cfg {String} nextText
107171      * The next month navigation button tooltip
107172      */
107173     nextText : 'Next Month (Control+Right)',
107174
107175     /**
107176      * @cfg {String} prevText
107177      * The previous month navigation button tooltip
107178      */
107179     prevText : 'Previous Month (Control+Left)',
107180
107181     /**
107182      * @cfg {String} monthYearText
107183      * The header month selector tooltip
107184      */
107185     monthYearText : 'Choose a month (Control+Up/Down to move years)',
107186
107187     /**
107188      * @cfg {Number} startDay
107189      * Day index at which the week should begin, 0-based (defaults to Sunday)
107190      */
107191     startDay : 0,
107192
107193     /**
107194      * @cfg {Boolean} showToday
107195      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
107196      * selects the current date.
107197      */
107198     showToday : true,
107199
107200     /**
107201      * @cfg {Date} [minDate=null]
107202      * Minimum allowable date (JavaScript date object)
107203      */
107204
107205     /**
107206      * @cfg {Date} [maxDate=null]
107207      * Maximum allowable date (JavaScript date object)
107208      */
107209
107210     /**
107211      * @cfg {Number[]} [disabledDays=null]
107212      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
107213      */
107214
107215     /**
107216      * @cfg {RegExp} [disabledDatesRE=null]
107217      * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
107218      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
107219      * disabledDates value.
107220      */
107221
107222     /**
107223      * @cfg {String[]} disabledDates
107224      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
107225      * they are very powerful. Some examples:
107226      *
107227      *   - ['03/08/2003', '09/16/2003'] would disable those exact dates
107228      *   - ['03/08', '09/16'] would disable those days for every year
107229      *   - ['^03/08'] would only match the beginning (useful if you are using short years)
107230      *   - ['03/../2006'] would disable every day in March 2006
107231      *   - ['^03'] would disable every day in every March
107232      *
107233      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
107234      * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
107235      * dot when restricting dates. For example: ['03\\.08\\.03'].
107236      */
107237
107238     /**
107239      * @cfg {Boolean} disableAnim
107240      * True to disable animations when showing the month picker.
107241      */
107242     disableAnim: false,
107243
107244     /**
107245      * @cfg {String} [baseCls='x-datepicker']
107246      * The base CSS class to apply to this components element.
107247      */
107248     baseCls: Ext.baseCSSPrefix + 'datepicker',
107249
107250     /**
107251      * @cfg {String} [selectedCls='x-datepicker-selected']
107252      * The class to apply to the selected cell.
107253      */
107254
107255     /**
107256      * @cfg {String} [disabledCellCls='x-datepicker-disabled']
107257      * The class to apply to disabled cells.
107258      */
107259
107260     /**
107261      * @cfg {String} longDayFormat
107262      * The format for displaying a date in a longer format.
107263      */
107264     longDayFormat: 'F d, Y',
107265
107266     /**
107267      * @cfg {Object} keyNavConfig
107268      * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
107269      * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
107270      * object will replace default handlers of the same name.
107271      */
107272
107273     /**
107274      * @cfg {Boolean} focusOnShow
107275      * True to automatically focus the picker on show.
107276      */
107277     focusOnShow: false,
107278
107279     // private
107280     // Set by other components to stop the picker focus being updated when the value changes.
107281     focusOnSelect: true,
107282
107283     width: 178,
107284
107285     // default value used to initialise each date in the DatePicker
107286     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
107287     initHour: 12, // 24-hour format
107288
107289     numDays: 42,
107290
107291     // private, inherit docs
107292     initComponent : function() {
107293         var me = this,
107294             clearTime = Ext.Date.clearTime;
107295
107296         me.selectedCls = me.baseCls + '-selected';
107297         me.disabledCellCls = me.baseCls + '-disabled';
107298         me.prevCls = me.baseCls + '-prevday';
107299         me.activeCls = me.baseCls + '-active';
107300         me.nextCls = me.baseCls + '-prevday';
107301         me.todayCls = me.baseCls + '-today';
107302         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
107303         this.callParent();
107304
107305         me.value = me.value ?
107306                  clearTime(me.value, true) : clearTime(new Date());
107307
107308         me.addEvents(
107309             /**
107310              * @event select
107311              * Fires when a date is selected
107312              * @param {Ext.picker.Date} this DatePicker
107313              * @param {Date} date The selected date
107314              */
107315             'select'
107316         );
107317
107318         me.initDisabledDays();
107319     },
107320
107321     // private, inherit docs
107322     onRender : function(container, position){
107323         /*
107324          * days array for looping through 6 full weeks (6 weeks * 7 days)
107325          * Note that we explicitly force the size here so the template creates
107326          * all the appropriate cells.
107327          */
107328
107329         var me = this,
107330             days = new Array(me.numDays),
107331             today = Ext.Date.format(new Date(), me.format);
107332
107333         Ext.applyIf(me, {
107334             renderData: {}
107335         });
107336
107337         Ext.apply(me.renderData, {
107338             dayNames: me.dayNames,
107339             ariaTitle: me.ariaTitle,
107340             value: me.value,
107341             showToday: me.showToday,
107342             prevText: me.prevText,
107343             nextText: me.nextText,
107344             days: days
107345         });
107346         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
107347
107348         me.addChildEls('eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
107349
107350         this.callParent(arguments);
107351         me.el.unselectable();
107352
107353         me.cells = me.eventEl.select('tbody td');
107354         me.textNodes = me.eventEl.query('tbody td span');
107355
107356         me.monthBtn = Ext.create('Ext.button.Split', {
107357             text: '',
107358             tooltip: me.monthYearText,
107359             renderTo: me.middleBtnEl
107360         });
107361         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
107362
107363
107364         me.todayBtn = Ext.create('Ext.button.Button', {
107365             renderTo: me.footerEl,
107366             text: Ext.String.format(me.todayText, today),
107367             tooltip: Ext.String.format(me.todayTip, today),
107368             handler: me.selectToday,
107369             scope: me
107370         });
107371     },
107372
107373     // private, inherit docs
107374     initEvents: function(){
107375         var me = this,
107376             eDate = Ext.Date,
107377             day = eDate.DAY;
107378
107379         this.callParent();
107380
107381         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
107382             handler: me.showPrevMonth,
107383             scope: me,
107384             preventDefault: true,
107385             stopDefault: true
107386         });
107387
107388         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
107389             handler: me.showNextMonth,
107390             scope: me,
107391             preventDefault:true,
107392             stopDefault:true
107393         });
107394
107395         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
107396             scope: me,
107397             'left' : function(e){
107398                 if(e.ctrlKey){
107399                     me.showPrevMonth();
107400                 }else{
107401                     me.update(eDate.add(me.activeDate, day, -1));
107402                 }
107403             },
107404
107405             'right' : function(e){
107406                 if(e.ctrlKey){
107407                     me.showNextMonth();
107408                 }else{
107409                     me.update(eDate.add(me.activeDate, day, 1));
107410                 }
107411             },
107412
107413             'up' : function(e){
107414                 if(e.ctrlKey){
107415                     me.showNextYear();
107416                 }else{
107417                     me.update(eDate.add(me.activeDate, day, -7));
107418                 }
107419             },
107420
107421             'down' : function(e){
107422                 if(e.ctrlKey){
107423                     me.showPrevYear();
107424                 }else{
107425                     me.update(eDate.add(me.activeDate, day, 7));
107426                 }
107427             },
107428             'pageUp' : me.showNextMonth,
107429             'pageDown' : me.showPrevMonth,
107430             'enter' : function(e){
107431                 e.stopPropagation();
107432                 return true;
107433             }
107434         }, me.keyNavConfig));
107435
107436         if(me.showToday){
107437             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
107438         }
107439         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
107440         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
107441         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
107442         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
107443         me.update(me.value);
107444     },
107445
107446     /**
107447      * Setup the disabled dates regex based on config options
107448      * @private
107449      */
107450     initDisabledDays : function(){
107451         var me = this,
107452             dd = me.disabledDates,
107453             re = '(?:',
107454             len;
107455
107456         if(!me.disabledDatesRE && dd){
107457                 len = dd.length - 1;
107458
107459             Ext.each(dd, function(d, i){
107460                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
107461                 if(i != len){
107462                     re += '|';
107463                 }
107464             }, me);
107465             me.disabledDatesRE = new RegExp(re + ')');
107466         }
107467     },
107468
107469     /**
107470      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
107471      * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
107472      * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
107473      * @return {Ext.picker.Date} this
107474      */
107475     setDisabledDates : function(dd){
107476         var me = this;
107477
107478         if(Ext.isArray(dd)){
107479             me.disabledDates = dd;
107480             me.disabledDatesRE = null;
107481         }else{
107482             me.disabledDatesRE = dd;
107483         }
107484         me.initDisabledDays();
107485         me.update(me.value, true);
107486         return me;
107487     },
107488
107489     /**
107490      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
107491      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
107492      * on supported values.
107493      * @return {Ext.picker.Date} this
107494      */
107495     setDisabledDays : function(dd){
107496         this.disabledDays = dd;
107497         return this.update(this.value, true);
107498     },
107499
107500     /**
107501      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
107502      * @param {Date} value The minimum date that can be selected
107503      * @return {Ext.picker.Date} this
107504      */
107505     setMinDate : function(dt){
107506         this.minDate = dt;
107507         return this.update(this.value, true);
107508     },
107509
107510     /**
107511      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
107512      * @param {Date} value The maximum date that can be selected
107513      * @return {Ext.picker.Date} this
107514      */
107515     setMaxDate : function(dt){
107516         this.maxDate = dt;
107517         return this.update(this.value, true);
107518     },
107519
107520     /**
107521      * Sets the value of the date field
107522      * @param {Date} value The date to set
107523      * @return {Ext.picker.Date} this
107524      */
107525     setValue : function(value){
107526         this.value = Ext.Date.clearTime(value, true);
107527         return this.update(this.value);
107528     },
107529
107530     /**
107531      * Gets the current selected value of the date field
107532      * @return {Date} The selected date
107533      */
107534     getValue : function(){
107535         return this.value;
107536     },
107537
107538     // private
107539     focus : function(){
107540         this.update(this.activeDate);
107541     },
107542
107543     // private, inherit docs
107544     onEnable: function(){
107545         this.callParent();
107546         this.setDisabledStatus(false);
107547         this.update(this.activeDate);
107548
107549     },
107550
107551     // private, inherit docs
107552     onDisable : function(){
107553         this.callParent();
107554         this.setDisabledStatus(true);
107555     },
107556
107557     /**
107558      * Set the disabled state of various internal components
107559      * @private
107560      * @param {Boolean} disabled
107561      */
107562     setDisabledStatus : function(disabled){
107563         var me = this;
107564
107565         me.keyNav.setDisabled(disabled);
107566         me.prevRepeater.setDisabled(disabled);
107567         me.nextRepeater.setDisabled(disabled);
107568         if (me.showToday) {
107569             me.todayKeyListener.setDisabled(disabled);
107570             me.todayBtn.setDisabled(disabled);
107571         }
107572     },
107573
107574     /**
107575      * Get the current active date.
107576      * @private
107577      * @return {Date} The active date
107578      */
107579     getActive: function(){
107580         return this.activeDate || this.value;
107581     },
107582
107583     /**
107584      * Run any animation required to hide/show the month picker.
107585      * @private
107586      * @param {Boolean} isHide True if it's a hide operation
107587      */
107588     runAnimation: function(isHide){
107589         var picker = this.monthPicker,
107590             options = {
107591                 duration: 200,
107592                 callback: function(){
107593                     if (isHide) {
107594                         picker.hide();
107595                     } else {
107596                         picker.show();
107597                     }
107598                 }
107599             };
107600
107601         if (isHide) {
107602             picker.el.slideOut('t', options);
107603         } else {
107604             picker.el.slideIn('t', options);
107605         }
107606     },
107607
107608     /**
107609      * Hides the month picker, if it's visible.
107610      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
107611      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
107612      * whether to animate or not.
107613      * @return {Ext.picker.Date} this
107614      */
107615     hideMonthPicker : function(animate){
107616         var me = this,
107617             picker = me.monthPicker;
107618
107619         if (picker) {
107620             if (me.shouldAnimate(animate)) {
107621                 me.runAnimation(true);
107622             } else {
107623                 picker.hide();
107624             }
107625         }
107626         return me;
107627     },
107628
107629     /**
107630      * Show the month picker
107631      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
107632      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
107633      * whether to animate or not.
107634      * @return {Ext.picker.Date} this
107635      */
107636     showMonthPicker : function(animate){
107637         var me = this,
107638             picker;
107639         
107640         if (me.rendered && !me.disabled) {
107641             picker = me.createMonthPicker();
107642             picker.setValue(me.getActive());
107643             picker.setSize(me.getSize());
107644             picker.setPosition(-1, -1);
107645             if (me.shouldAnimate(animate)) {
107646                 me.runAnimation(false);
107647             } else {
107648                 picker.show();
107649             }
107650         }
107651         return me;
107652     },
107653     
107654     /**
107655      * Checks whether a hide/show action should animate
107656      * @private
107657      * @param {Boolean} [animate] A possible animation value
107658      * @return {Boolean} Whether to animate the action
107659      */
107660     shouldAnimate: function(animate){
107661         return Ext.isDefined(animate) ? animate : !this.disableAnim;
107662     },
107663
107664     /**
107665      * Create the month picker instance
107666      * @private
107667      * @return {Ext.picker.Month} picker
107668      */
107669     createMonthPicker: function(){
107670         var me = this,
107671             picker = me.monthPicker;
107672
107673         if (!picker) {
107674             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
107675                 renderTo: me.el,
107676                 floating: true,
107677                 shadow: false,
107678                 small: me.showToday === false,
107679                 listeners: {
107680                     scope: me,
107681                     cancelclick: me.onCancelClick,
107682                     okclick: me.onOkClick,
107683                     yeardblclick: me.onOkClick,
107684                     monthdblclick: me.onOkClick
107685                 }
107686             });
107687             if (!me.disableAnim) {
107688                 // hide the element if we're animating to prevent an initial flicker
107689                 picker.el.setStyle('display', 'none');
107690             }
107691             me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
107692         }
107693         return picker;
107694     },
107695
107696     /**
107697      * Respond to an ok click on the month picker
107698      * @private
107699      */
107700     onOkClick: function(picker, value){
107701         var me = this,
107702             month = value[0],
107703             year = value[1],
107704             date = new Date(year, month, me.getActive().getDate());
107705
107706         if (date.getMonth() !== month) {
107707             // 'fix' the JS rolling date conversion if needed
107708             date = new Date(year, month, 1).getLastDateOfMonth();
107709         }
107710         me.update(date);
107711         me.hideMonthPicker();
107712     },
107713
107714     /**
107715      * Respond to a cancel click on the month picker
107716      * @private
107717      */
107718     onCancelClick: function(){
107719         this.hideMonthPicker();
107720     },
107721
107722     /**
107723      * Show the previous month.
107724      * @param {Object} e
107725      * @return {Ext.picker.Date} this
107726      */
107727     showPrevMonth : function(e){
107728         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
107729     },
107730
107731     /**
107732      * Show the next month.
107733      * @param {Object} e
107734      * @return {Ext.picker.Date} this
107735      */
107736     showNextMonth : function(e){
107737         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
107738     },
107739
107740     /**
107741      * Show the previous year.
107742      * @return {Ext.picker.Date} this
107743      */
107744     showPrevYear : function(){
107745         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
107746     },
107747
107748     /**
107749      * Show the next year.
107750      * @return {Ext.picker.Date} this
107751      */
107752     showNextYear : function(){
107753         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
107754     },
107755
107756     /**
107757      * Respond to the mouse wheel event
107758      * @private
107759      * @param {Ext.EventObject} e
107760      */
107761     handleMouseWheel : function(e){
107762         e.stopEvent();
107763         if(!this.disabled){
107764             var delta = e.getWheelDelta();
107765             if(delta > 0){
107766                 this.showPrevMonth();
107767             } else if(delta < 0){
107768                 this.showNextMonth();
107769             }
107770         }
107771     },
107772
107773     /**
107774      * Respond to a date being clicked in the picker
107775      * @private
107776      * @param {Ext.EventObject} e
107777      * @param {HTMLElement} t
107778      */
107779     handleDateClick : function(e, t){
107780         var me = this,
107781             handler = me.handler;
107782
107783         e.stopEvent();
107784         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
107785             me.cancelFocus = me.focusOnSelect === false;
107786             me.setValue(new Date(t.dateValue));
107787             delete me.cancelFocus;
107788             me.fireEvent('select', me, me.value);
107789             if (handler) {
107790                 handler.call(me.scope || me, me, me.value);
107791             }
107792             // event handling is turned off on hide
107793             // when we are using the picker in a field
107794             // therefore onSelect comes AFTER the select
107795             // event.
107796             me.onSelect();
107797         }
107798     },
107799
107800     /**
107801      * Perform any post-select actions
107802      * @private
107803      */
107804     onSelect: function() {
107805         if (this.hideOnSelect) {
107806              this.hide();
107807          }
107808     },
107809
107810     /**
107811      * Sets the current value to today.
107812      * @return {Ext.picker.Date} this
107813      */
107814     selectToday : function(){
107815         var me = this,
107816             btn = me.todayBtn,
107817             handler = me.handler;
107818
107819         if(btn && !btn.disabled){
107820             me.setValue(Ext.Date.clearTime(new Date()));
107821             me.fireEvent('select', me, me.value);
107822             if (handler) {
107823                 handler.call(me.scope || me, me, me.value);
107824             }
107825             me.onSelect();
107826         }
107827         return me;
107828     },
107829
107830     /**
107831      * Update the selected cell
107832      * @private
107833      * @param {Date} date The new date
107834      * @param {Date} active The active date
107835      */
107836     selectedUpdate: function(date, active){
107837         var me = this,
107838             t = date.getTime(),
107839             cells = me.cells,
107840             cls = me.selectedCls;
107841
107842         cells.removeCls(cls);
107843         cells.each(function(c){
107844             if (c.dom.firstChild.dateValue == t) {
107845                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
107846                 c.addCls(cls);
107847                 if(me.isVisible() && !me.cancelFocus){
107848                     Ext.fly(c.dom.firstChild).focus(50);
107849                 }
107850                 return false;
107851             }
107852         }, this);
107853     },
107854
107855     /**
107856      * Update the contents of the picker for a new month
107857      * @private
107858      * @param {Date} date The new date
107859      * @param {Date} active The active date
107860      */
107861     fullUpdate: function(date, active){
107862         var me = this,
107863             cells = me.cells.elements,
107864             textNodes = me.textNodes,
107865             disabledCls = me.disabledCellCls,
107866             eDate = Ext.Date,
107867             i = 0,
107868             extraDays = 0,
107869             visible = me.isVisible(),
107870             sel = +eDate.clearTime(date, true),
107871             today = +eDate.clearTime(new Date()),
107872             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
107873             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
107874             ddMatch = me.disabledDatesRE,
107875             ddText = me.disabledDatesText,
107876             ddays = me.disabledDays ? me.disabledDays.join('') : false,
107877             ddaysText = me.disabledDaysText,
107878             format = me.format,
107879             days = eDate.getDaysInMonth(date),
107880             firstOfMonth = eDate.getFirstDateOfMonth(date),
107881             startingPos = firstOfMonth.getDay() - me.startDay,
107882             previousMonth = eDate.add(date, eDate.MONTH, -1),
107883             longDayFormat = me.longDayFormat,
107884             prevStart,
107885             current,
107886             disableToday,
107887             tempDate,
107888             setCellClass,
107889             html,
107890             cls,
107891             formatValue,
107892             value;
107893
107894         if (startingPos < 0) {
107895             startingPos += 7;
107896         }
107897
107898         days += startingPos;
107899         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
107900         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
107901
107902         if (me.showToday) {
107903             tempDate = eDate.clearTime(new Date());
107904             disableToday = (tempDate < min || tempDate > max ||
107905                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
107906                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
107907
107908             if (!me.disabled) {
107909                 me.todayBtn.setDisabled(disableToday);
107910                 me.todayKeyListener.setDisabled(disableToday);
107911             }
107912         }
107913
107914         setCellClass = function(cell){
107915             value = +eDate.clearTime(current, true);
107916             cell.title = eDate.format(current, longDayFormat);
107917             // store dateValue number as an expando
107918             cell.firstChild.dateValue = value;
107919             if(value == today){
107920                 cell.className += ' ' + me.todayCls;
107921                 cell.title = me.todayText;
107922             }
107923             if(value == sel){
107924                 cell.className += ' ' + me.selectedCls;
107925                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
107926                 if (visible && me.floating) {
107927                     Ext.fly(cell.firstChild).focus(50);
107928                 }
107929             }
107930             // disabling
107931             if(value < min) {
107932                 cell.className = disabledCls;
107933                 cell.title = me.minText;
107934                 return;
107935             }
107936             if(value > max) {
107937                 cell.className = disabledCls;
107938                 cell.title = me.maxText;
107939                 return;
107940             }
107941             if(ddays){
107942                 if(ddays.indexOf(current.getDay()) != -1){
107943                     cell.title = ddaysText;
107944                     cell.className = disabledCls;
107945                 }
107946             }
107947             if(ddMatch && format){
107948                 formatValue = eDate.dateFormat(current, format);
107949                 if(ddMatch.test(formatValue)){
107950                     cell.title = ddText.replace('%0', formatValue);
107951                     cell.className = disabledCls;
107952                 }
107953             }
107954         };
107955
107956         for(; i < me.numDays; ++i) {
107957             if (i < startingPos) {
107958                 html = (++prevStart);
107959                 cls = me.prevCls;
107960             } else if (i >= days) {
107961                 html = (++extraDays);
107962                 cls = me.nextCls;
107963             } else {
107964                 html = i - startingPos + 1;
107965                 cls = me.activeCls;
107966             }
107967             textNodes[i].innerHTML = html;
107968             cells[i].className = cls;
107969             current.setDate(current.getDate() + 1);
107970             setCellClass(cells[i]);
107971         }
107972
107973         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
107974     },
107975
107976     /**
107977      * Update the contents of the picker
107978      * @private
107979      * @param {Date} date The new date
107980      * @param {Boolean} forceRefresh True to force a full refresh
107981      */
107982     update : function(date, forceRefresh){
107983         var me = this,
107984             active = me.activeDate;
107985
107986         if (me.rendered) {
107987             me.activeDate = date;
107988             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
107989                 me.selectedUpdate(date, active);
107990             } else {
107991                 me.fullUpdate(date, active);
107992             }
107993         }
107994         return me;
107995     },
107996
107997     // private, inherit docs
107998     beforeDestroy : function() {
107999         var me = this;
108000
108001         if (me.rendered) {
108002             Ext.destroy(
108003                 me.todayKeyListener,
108004                 me.keyNav,
108005                 me.monthPicker,
108006                 me.monthBtn,
108007                 me.nextRepeater,
108008                 me.prevRepeater,
108009                 me.todayBtn
108010             );
108011             delete me.textNodes;
108012             delete me.cells.elements;
108013         }
108014         me.callParent();
108015     },
108016
108017     // private, inherit docs
108018     onShow: function() {
108019         this.callParent(arguments);
108020         if (this.focusOnShow) {
108021             this.focus();
108022         }
108023     }
108024 },
108025
108026 // After dependencies have loaded:
108027 function() {
108028     var proto = this.prototype;
108029
108030     proto.monthNames = Ext.Date.monthNames;
108031
108032     proto.dayNames = Ext.Date.dayNames;
108033
108034     proto.format = Ext.Date.defaultFormat;
108035 });
108036
108037 /**
108038  * @docauthor Jason Johnston <jason@sencha.com>
108039  *
108040  * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
108041  * validation.
108042  *
108043  * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
108044  * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
108045  * configs. These may be reconfigured to use date formats appropriate for the user's locale.
108046  *
108047  * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
108048  * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
108049  * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
108050  *
108051  * # Example usage
108052  *
108053  *     @example
108054  *     Ext.create('Ext.form.Panel', {
108055  *         renderTo: Ext.getBody(),
108056  *         width: 300,
108057  *         bodyPadding: 10,
108058  *         title: 'Dates',
108059  *         items: [{
108060  *             xtype: 'datefield',
108061  *             anchor: '100%',
108062  *             fieldLabel: 'From',
108063  *             name: 'from_date',
108064  *             maxValue: new Date()  // limited to the current date or prior
108065  *         }, {
108066  *             xtype: 'datefield',
108067  *             anchor: '100%',
108068  *             fieldLabel: 'To',
108069  *             name: 'to_date',
108070  *             value: new Date()  // defaults to today
108071  *         }]
108072  *     });
108073  *
108074  * # Date Formats Examples
108075  *
108076  * This example shows a couple of different date format parsing scenarios. Both use custom date format
108077  * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
108078  *
108079  *     @example
108080  *     Ext.create('Ext.form.Panel', {
108081  *         renderTo: Ext.getBody(),
108082  *         width: 300,
108083  *         bodyPadding: 10,
108084  *         title: 'Dates',
108085  *         items: [{
108086  *             xtype: 'datefield',
108087  *             anchor: '100%',
108088  *             fieldLabel: 'Date',
108089  *             name: 'date',
108090  *             // The value matches the format; will be parsed and displayed using that format.
108091  *             format: 'm d Y',
108092  *             value: '2 4 1978'
108093  *         }, {
108094  *             xtype: 'datefield',
108095  *             anchor: '100%',
108096  *             fieldLabel: 'Date',
108097  *             name: 'date',
108098  *             // The value does not match the format, but does match an altFormat; will be parsed
108099  *             // using the altFormat and displayed using the format.
108100  *             format: 'm d Y',
108101  *             altFormats: 'm,d,Y|m.d.Y',
108102  *             value: '2.4.1978'
108103  *         }]
108104  *     });
108105  */
108106 Ext.define('Ext.form.field.Date', {
108107     extend:'Ext.form.field.Picker',
108108     alias: 'widget.datefield',
108109     requires: ['Ext.picker.Date'],
108110     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
108111
108112     /**
108113      * @cfg {String} format
108114      * The default date format string which can be overriden for localization support. The format must be valid
108115      * according to {@link Ext.Date#parse}.
108116      */
108117     format : "m/d/Y",
108118     /**
108119      * @cfg {String} altFormats
108120      * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
108121      * format.
108122      */
108123     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",
108124     /**
108125      * @cfg {String} disabledDaysText
108126      * The tooltip to display when the date falls on a disabled day.
108127      */
108128     disabledDaysText : "Disabled",
108129     /**
108130      * @cfg {String} disabledDatesText
108131      * The tooltip text to display when the date falls on a disabled date.
108132      */
108133     disabledDatesText : "Disabled",
108134     /**
108135      * @cfg {String} minText
108136      * The error text to display when the date in the cell is before {@link #minValue}.
108137      */
108138     minText : "The date in this field must be equal to or after {0}",
108139     /**
108140      * @cfg {String} maxText
108141      * The error text to display when the date in the cell is after {@link #maxValue}.
108142      */
108143     maxText : "The date in this field must be equal to or before {0}",
108144     /**
108145      * @cfg {String} invalidText
108146      * The error text to display when the date in the field is invalid.
108147      */
108148     invalidText : "{0} is not a valid date - it must be in the format {1}",
108149     /**
108150      * @cfg {String} [triggerCls='x-form-date-trigger']
108151      * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
108152      * and triggerCls will be **appended** if specified (default class displays a calendar icon).
108153      */
108154     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
108155     /**
108156      * @cfg {Boolean} showToday
108157      * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
108158      * spacebar that selects the current date.
108159      */
108160     showToday : true,
108161     /**
108162      * @cfg {Date/String} minValue
108163      * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
108164      */
108165     /**
108166      * @cfg {Date/String} maxValue
108167      * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
108168      */
108169     /**
108170      * @cfg {Number[]} disabledDays
108171      * An array of days to disable, 0 based. Some examples:
108172      *
108173      *     // disable Sunday and Saturday:
108174      *     disabledDays:  [0, 6]
108175      *     // disable weekdays:
108176      *     disabledDays: [1,2,3,4,5]
108177      */
108178     /**
108179      * @cfg {String[]} disabledDates
108180      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
108181      * they are very powerful. Some examples:
108182      *
108183      *     // disable these exact dates:
108184      *     disabledDates: ["03/08/2003", "09/16/2003"]
108185      *     // disable these days for every year:
108186      *     disabledDates: ["03/08", "09/16"]
108187      *     // only match the beginning (useful if you are using short years):
108188      *     disabledDates: ["^03/08"]
108189      *     // disable every day in March 2006:
108190      *     disabledDates: ["03/../2006"]
108191      *     // disable every day in every March:
108192      *     disabledDates: ["^03"]
108193      *
108194      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
108195      * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
108196      * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
108197      */
108198
108199     /**
108200      * @cfg {String} submitFormat
108201      * The date format string which will be submitted to the server. The format must be valid according to {@link
108202      * Ext.Date#parse} (defaults to {@link #format}).
108203      */
108204
108205     // in the absence of a time value, a default value of 12 noon will be used
108206     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
108207     initTime: '12', // 24 hour format
108208
108209     initTimeFormat: 'H',
108210
108211     matchFieldWidth: false,
108212     /**
108213      * @cfg {Number} startDay
108214      * Day index at which the week should begin, 0-based (defaults to Sunday)
108215      */
108216     startDay: 0,
108217
108218     initComponent : function(){
108219         var me = this,
108220             isString = Ext.isString,
108221             min, max;
108222
108223         min = me.minValue;
108224         max = me.maxValue;
108225         if(isString(min)){
108226             me.minValue = me.parseDate(min);
108227         }
108228         if(isString(max)){
108229             me.maxValue = me.parseDate(max);
108230         }
108231         me.disabledDatesRE = null;
108232         me.initDisabledDays();
108233
108234         me.callParent();
108235     },
108236
108237     initValue: function() {
108238         var me = this,
108239             value = me.value;
108240
108241         // If a String value was supplied, try to convert it to a proper Date
108242         if (Ext.isString(value)) {
108243             me.value = me.rawToValue(value);
108244         }
108245
108246         me.callParent();
108247     },
108248
108249     // private
108250     initDisabledDays : function(){
108251         if(this.disabledDates){
108252             var dd = this.disabledDates,
108253                 len = dd.length - 1,
108254                 re = "(?:";
108255
108256             Ext.each(dd, function(d, i){
108257                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
108258                 if (i !== len) {
108259                     re += '|';
108260                 }
108261             }, this);
108262             this.disabledDatesRE = new RegExp(re + ')');
108263         }
108264     },
108265
108266     /**
108267      * Replaces any existing disabled dates with new values and refreshes the Date picker.
108268      * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
108269      * supported values) used to disable a pattern of dates.
108270      */
108271     setDisabledDates : function(dd){
108272         var me = this,
108273             picker = me.picker;
108274
108275         me.disabledDates = dd;
108276         me.initDisabledDays();
108277         if (picker) {
108278             picker.setDisabledDates(me.disabledDatesRE);
108279         }
108280     },
108281
108282     /**
108283      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
108284      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
108285      * supported values.
108286      */
108287     setDisabledDays : function(dd){
108288         var picker = this.picker;
108289
108290         this.disabledDays = dd;
108291         if (picker) {
108292             picker.setDisabledDays(dd);
108293         }
108294     },
108295
108296     /**
108297      * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
108298      * @param {Date} value The minimum date that can be selected
108299      */
108300     setMinValue : function(dt){
108301         var me = this,
108302             picker = me.picker,
108303             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
108304
108305         me.minValue = minValue;
108306         if (picker) {
108307             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
108308             picker.setMinDate(minValue);
108309         }
108310     },
108311
108312     /**
108313      * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
108314      * @param {Date} value The maximum date that can be selected
108315      */
108316     setMaxValue : function(dt){
108317         var me = this,
108318             picker = me.picker,
108319             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
108320
108321         me.maxValue = maxValue;
108322         if (picker) {
108323             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
108324             picker.setMaxDate(maxValue);
108325         }
108326     },
108327
108328     /**
108329      * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
108330      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
108331      * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
108332      * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
108333      * @param {Object} [value] The value to get errors for (defaults to the current field value)
108334      * @return {String[]} All validation errors for this field
108335      */
108336     getErrors: function(value) {
108337         var me = this,
108338             format = Ext.String.format,
108339             clearTime = Ext.Date.clearTime,
108340             errors = me.callParent(arguments),
108341             disabledDays = me.disabledDays,
108342             disabledDatesRE = me.disabledDatesRE,
108343             minValue = me.minValue,
108344             maxValue = me.maxValue,
108345             len = disabledDays ? disabledDays.length : 0,
108346             i = 0,
108347             svalue,
108348             fvalue,
108349             day,
108350             time;
108351
108352         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
108353
108354         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
108355              return errors;
108356         }
108357
108358         svalue = value;
108359         value = me.parseDate(value);
108360         if (!value) {
108361             errors.push(format(me.invalidText, svalue, me.format));
108362             return errors;
108363         }
108364
108365         time = value.getTime();
108366         if (minValue && time < clearTime(minValue).getTime()) {
108367             errors.push(format(me.minText, me.formatDate(minValue)));
108368         }
108369
108370         if (maxValue && time > clearTime(maxValue).getTime()) {
108371             errors.push(format(me.maxText, me.formatDate(maxValue)));
108372         }
108373
108374         if (disabledDays) {
108375             day = value.getDay();
108376
108377             for(; i < len; i++) {
108378                 if (day === disabledDays[i]) {
108379                     errors.push(me.disabledDaysText);
108380                     break;
108381                 }
108382             }
108383         }
108384
108385         fvalue = me.formatDate(value);
108386         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
108387             errors.push(format(me.disabledDatesText, fvalue));
108388         }
108389
108390         return errors;
108391     },
108392
108393     rawToValue: function(rawValue) {
108394         return this.parseDate(rawValue) || rawValue || null;
108395     },
108396
108397     valueToRaw: function(value) {
108398         return this.formatDate(this.parseDate(value));
108399     },
108400
108401     /**
108402      * @method setValue
108403      * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
108404      * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
108405      * format used is "m/d/Y").
108406      *
108407      * Usage:
108408      *
108409      *     //All of these calls set the same date value (May 4, 2006)
108410      *
108411      *     //Pass a date object:
108412      *     var dt = new Date('5/4/2006');
108413      *     dateField.setValue(dt);
108414      *
108415      *     //Pass a date string (default format):
108416      *     dateField.setValue('05/04/2006');
108417      *
108418      *     //Pass a date string (custom format):
108419      *     dateField.format = 'Y-m-d';
108420      *     dateField.setValue('2006-05-04');
108421      *
108422      * @param {String/Date} date The date or valid date string
108423      * @return {Ext.form.field.Date} this
108424      */
108425
108426     /**
108427      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
108428      * @param {String} value The value to attempt to parse
108429      * @param {String} format A valid date format (see {@link Ext.Date#parse})
108430      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
108431      */
108432     safeParse : function(value, format) {
108433         var me = this,
108434             utilDate = Ext.Date,
108435             parsedDate,
108436             result = null;
108437
108438         if (utilDate.formatContainsHourInfo(format)) {
108439             // if parse format contains hour information, no DST adjustment is necessary
108440             result = utilDate.parse(value, format);
108441         } else {
108442             // set time to 12 noon, then clear the time
108443             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
108444             if (parsedDate) {
108445                 result = utilDate.clearTime(parsedDate);
108446             }
108447         }
108448         return result;
108449     },
108450
108451     // @private
108452     getSubmitValue: function() {
108453         var format = this.submitFormat || this.format,
108454             value = this.getValue();
108455
108456         return value ? Ext.Date.format(value, format) : '';
108457     },
108458
108459     /**
108460      * @private
108461      */
108462     parseDate : function(value) {
108463         if(!value || Ext.isDate(value)){
108464             return value;
108465         }
108466
108467         var me = this,
108468             val = me.safeParse(value, me.format),
108469             altFormats = me.altFormats,
108470             altFormatsArray = me.altFormatsArray,
108471             i = 0,
108472             len;
108473
108474         if (!val && altFormats) {
108475             altFormatsArray = altFormatsArray || altFormats.split('|');
108476             len = altFormatsArray.length;
108477             for (; i < len && !val; ++i) {
108478                 val = me.safeParse(value, altFormatsArray[i]);
108479             }
108480         }
108481         return val;
108482     },
108483
108484     // private
108485     formatDate : function(date){
108486         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
108487     },
108488
108489     createPicker: function() {
108490         var me = this,
108491             format = Ext.String.format;
108492
108493         return Ext.create('Ext.picker.Date', {
108494             pickerField: me,
108495             ownerCt: me.ownerCt,
108496             renderTo: document.body,
108497             floating: true,
108498             hidden: true,
108499             focusOnShow: true,
108500             minDate: me.minValue,
108501             maxDate: me.maxValue,
108502             disabledDatesRE: me.disabledDatesRE,
108503             disabledDatesText: me.disabledDatesText,
108504             disabledDays: me.disabledDays,
108505             disabledDaysText: me.disabledDaysText,
108506             format: me.format,
108507             showToday: me.showToday,
108508             startDay: me.startDay,
108509             minText: format(me.minText, me.formatDate(me.minValue)),
108510             maxText: format(me.maxText, me.formatDate(me.maxValue)),
108511             listeners: {
108512                 scope: me,
108513                 select: me.onSelect
108514             },
108515             keyNavConfig: {
108516                 esc: function() {
108517                     me.collapse();
108518                 }
108519             }
108520         });
108521     },
108522
108523     onSelect: function(m, d) {
108524         var me = this;
108525
108526         me.setValue(d);
108527         me.fireEvent('select', me, d);
108528         me.collapse();
108529     },
108530
108531     /**
108532      * @private
108533      * Sets the Date picker's value to match the current field value when expanding.
108534      */
108535     onExpand: function() {
108536         var value = this.getValue();
108537         this.picker.setValue(Ext.isDate(value) ? value : new Date());
108538     },
108539
108540     /**
108541      * @private
108542      * Focuses the field when collapsing the Date picker.
108543      */
108544     onCollapse: function() {
108545         this.focus(false, 60);
108546     },
108547
108548     // private
108549     beforeBlur : function(){
108550         var me = this,
108551             v = me.parseDate(me.getRawValue()),
108552             focusTask = me.focusTask;
108553
108554         if (focusTask) {
108555             focusTask.cancel();
108556         }
108557
108558         if (v) {
108559             me.setValue(v);
108560         }
108561     }
108562
108563     /**
108564      * @hide
108565      * @cfg {Boolean} grow
108566      */
108567     /**
108568      * @hide
108569      * @cfg {Number} growMin
108570      */
108571     /**
108572      * @hide
108573      * @cfg {Number} growMax
108574      */
108575     /**
108576      * @hide
108577      * @method autoSize
108578      */
108579 });
108580
108581 /**
108582  * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
108583  * value from a form's {@link Ext.form.Basic#load loaded data} but do not want to allow the user to edit or submit that
108584  * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
108585  * to be rendered.
108586  *
108587  * If you have more complex content, or need to include components within the displayed content, also consider using a
108588  * {@link Ext.form.FieldContainer} instead.
108589  *
108590  * Example:
108591  *
108592  *     @example
108593  *     Ext.create('Ext.form.Panel', {
108594  *         renderTo: Ext.getBody(),
108595  *         width: 175,
108596  *         height: 120,
108597  *         bodyPadding: 10,
108598  *         title: 'Final Score',
108599  *         items: [{
108600  *             xtype: 'displayfield',
108601  *             fieldLabel: 'Home',
108602  *             name: 'home_score',
108603  *             value: '10'
108604  *         }, {
108605  *             xtype: 'displayfield',
108606  *             fieldLabel: 'Visitor',
108607  *             name: 'visitor_score',
108608  *             value: '11'
108609  *         }],
108610  *         buttons: [{
108611  *             text: 'Update',
108612  *         }]
108613  *     });
108614  */
108615 Ext.define('Ext.form.field.Display', {
108616     extend:'Ext.form.field.Base',
108617     alias: 'widget.displayfield',
108618     requires: ['Ext.util.Format', 'Ext.XTemplate'],
108619     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
108620     fieldSubTpl: [
108621         '<div id="{id}" class="{fieldCls}"></div>',
108622         {
108623             compiled: true,
108624             disableFormats: true
108625         }
108626     ],
108627
108628     /**
108629      * @cfg {String} [fieldCls="x-form-display-field"]
108630      * The default CSS class for the field.
108631      */
108632     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
108633
108634     /**
108635      * @cfg {Boolean} htmlEncode
108636      * false to skip HTML-encoding the text when rendering it. This might be useful if you want to
108637      * include tags in the field's innerHTML rather than rendering them as string literals per the default logic.
108638      */
108639     htmlEncode: false,
108640
108641     validateOnChange: false,
108642
108643     initEvents: Ext.emptyFn,
108644
108645     submitValue: false,
108646
108647     isValid: function() {
108648         return true;
108649     },
108650
108651     validate: function() {
108652         return true;
108653     },
108654
108655     getRawValue: function() {
108656         return this.rawValue;
108657     },
108658
108659     setRawValue: function(value) {
108660         var me = this;
108661         value = Ext.value(value, '');
108662         me.rawValue = value;
108663         if (me.rendered) {
108664             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
108665         }
108666         return value;
108667     },
108668
108669     // private
108670     getContentTarget: function() {
108671         return this.inputEl;
108672     }
108673
108674     /**
108675      * @cfg {String} inputType
108676      * @hide
108677      */
108678     /**
108679      * @cfg {Boolean} disabled
108680      * @hide
108681      */
108682     /**
108683      * @cfg {Boolean} readOnly
108684      * @hide
108685      */
108686     /**
108687      * @cfg {Boolean} validateOnChange
108688      * @hide
108689      */
108690     /**
108691      * @cfg {Number} checkChangeEvents
108692      * @hide
108693      */
108694     /**
108695      * @cfg {Number} checkChangeBuffer
108696      * @hide
108697      */
108698 });
108699
108700 /**
108701  * @docauthor Jason Johnston <jason@sencha.com>
108702  *
108703  * A file upload field which has custom styling and allows control over the button text and other
108704  * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
108705  * It uses a hidden file input element behind the scenes to allow user selection of a file and to
108706  * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
108707  *
108708  * Because there is no secure cross-browser way to programmatically set the value of a file input,
108709  * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
108710  * a value that is browser-dependent; some have just the file name, some have a full path, some use
108711  * a fake path.
108712  *
108713  * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
108714  * {@link Ext.form.Basic#hasUpload} for details.
108715  *
108716  * # Example Usage
108717  *
108718  *     @example
108719  *     Ext.create('Ext.form.Panel', {
108720  *         title: 'Upload a Photo',
108721  *         width: 400,
108722  *         bodyPadding: 10,
108723  *         frame: true,
108724  *         renderTo: Ext.getBody(),
108725  *         items: [{
108726  *             xtype: 'filefield',
108727  *             name: 'photo',
108728  *             fieldLabel: 'Photo',
108729  *             labelWidth: 50,
108730  *             msgTarget: 'side',
108731  *             allowBlank: false,
108732  *             anchor: '100%',
108733  *             buttonText: 'Select Photo...'
108734  *         }],
108735  *
108736  *         buttons: [{
108737  *             text: 'Upload',
108738  *             handler: function() {
108739  *                 var form = this.up('form').getForm();
108740  *                 if(form.isValid()){
108741  *                     form.submit({
108742  *                         url: 'photo-upload.php',
108743  *                         waitMsg: 'Uploading your photo...',
108744  *                         success: function(fp, o) {
108745  *                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
108746  *                         }
108747  *                     });
108748  *                 }
108749  *             }
108750  *         }]
108751  *     });
108752  */
108753 Ext.define("Ext.form.field.File", {
108754     extend: 'Ext.form.field.Text',
108755     alias: ['widget.filefield', 'widget.fileuploadfield'],
108756     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
108757     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
108758
108759     /**
108760      * @cfg {String} buttonText
108761      * The button text to display on the upload button. Note that if you supply a value for
108762      * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
108763      */
108764     buttonText: 'Browse...',
108765
108766     /**
108767      * @cfg {Boolean} buttonOnly
108768      * True to display the file upload field as a button with no visible text field. If true, all
108769      * inherited Text members will still be available.
108770      */
108771     buttonOnly: false,
108772
108773     /**
108774      * @cfg {Number} buttonMargin
108775      * The number of pixels of space reserved between the button and the text field. Note that this only
108776      * applies if {@link #buttonOnly} = false.
108777      */
108778     buttonMargin: 3,
108779
108780     /**
108781      * @cfg {Object} buttonConfig
108782      * A standard {@link Ext.button.Button} config object.
108783      */
108784
108785     /**
108786      * @event change
108787      * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
108788      * file selection dialog.
108789      * @param {Ext.ux.form.FileUploadField} this
108790      * @param {String} value The file value returned by the underlying file input field
108791      */
108792
108793     /**
108794      * @property {Ext.Element} fileInputEl
108795      * A reference to the invisible file input element created for this upload field. Only populated after this
108796      * component is rendered.
108797      */
108798
108799     /**
108800      * @property {Ext.button.Button} button
108801      * A reference to the trigger Button component created for this upload field. Only populated after this component is
108802      * rendered.
108803      */
108804
108805     /**
108806      * @cfg {String} [fieldBodyCls='x-form-file-wrap']
108807      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
108808      */
108809     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
108810
108811     /**
108812      * @cfg {Boolean} readOnly
108813      * Unlike with other form fields, the readOnly config defaults to true in File field.
108814      */
108815     readOnly: true,
108816
108817     // private
108818     componentLayout: 'filefield',
108819
108820     // private
108821     onRender: function() {
108822         var me = this,
108823             inputEl;
108824
108825         me.callParent(arguments);
108826
108827         me.createButton();
108828         me.createFileInput();
108829         
108830         // we don't create the file/button til after onRender, the initial disable() is
108831         // called in the onRender of the component.
108832         if (me.disabled) {
108833             me.disableItems();
108834         }
108835
108836         inputEl = me.inputEl;
108837         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
108838         if (me.buttonOnly) {
108839             inputEl.setDisplayed(false);
108840         }
108841     },
108842
108843     /**
108844      * @private
108845      * Creates the custom trigger Button component. The fileInput will be inserted into this.
108846      */
108847     createButton: function() {
108848         var me = this;
108849         me.button = Ext.widget('button', Ext.apply({
108850             ui: me.ui,
108851             renderTo: me.bodyEl,
108852             text: me.buttonText,
108853             cls: Ext.baseCSSPrefix + 'form-file-btn',
108854             preventDefault: false,
108855             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
108856         }, me.buttonConfig));
108857     },
108858
108859     /**
108860      * @private
108861      * Creates the file input element. It is inserted into the trigger button component, made
108862      * invisible, and floated on top of the button's other content so that it will receive the
108863      * button's clicks.
108864      */
108865     createFileInput : function() {
108866         var me = this;
108867         me.fileInputEl = me.button.el.createChild({
108868             name: me.getName(),
108869             cls: Ext.baseCSSPrefix + 'form-file-input',
108870             tag: 'input',
108871             type: 'file',
108872             size: 1
108873         }).on('change', me.onFileChange, me);
108874     },
108875
108876     /**
108877      * @private Event handler fired when the user selects a file.
108878      */
108879     onFileChange: function() {
108880         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
108881         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
108882     },
108883
108884     /**
108885      * Overridden to do nothing
108886      * @hide
108887      */
108888     setValue: Ext.emptyFn,
108889
108890     reset : function(){
108891         var me = this;
108892         if (me.rendered) {
108893             me.fileInputEl.remove();
108894             me.createFileInput();
108895             me.inputEl.dom.value = '';
108896         }
108897         me.callParent();
108898     },
108899
108900     onDisable: function(){
108901         this.callParent();
108902         this.disableItems();
108903     },
108904     
108905     disableItems: function(){
108906         var file = this.fileInputEl,
108907             button = this.button;
108908              
108909         if (file) {
108910             file.dom.disabled = true;
108911         }
108912         if (button) {
108913             button.disable();
108914         }    
108915     },
108916
108917     onEnable: function(){
108918         var me = this;
108919         me.callParent();
108920         me.fileInputEl.dom.disabled = false;
108921         me.button.enable();
108922     },
108923
108924     isFileUpload: function() {
108925         return true;
108926     },
108927
108928     extractFileInput: function() {
108929         var fileInput = this.fileInputEl.dom;
108930         this.reset();
108931         return fileInput;
108932     },
108933
108934     onDestroy: function(){
108935         Ext.destroyMembers(this, 'fileInputEl', 'button');
108936         this.callParent();
108937     }
108938
108939
108940 });
108941
108942 /**
108943  * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
108944  *
108945  * This creates an actual input element with type="submit" in the DOM. While its label is
108946  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
108947  * to its owner container's layout.
108948  *
108949  * Because of this, in most cases it is more convenient and less problematic to simply
108950  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
108951  * {@link Ext.form.Basic#submit submitting the form}.
108952  *
108953  * Example:
108954  *
108955  *     new Ext.form.Panel({
108956  *         title: 'My Form',
108957  *         items: [{
108958  *             xtype: 'textfield',
108959  *             fieldLabel: 'Text Field',
108960  *             name: 'text_field',
108961  *             value: 'value from text field'
108962  *         }, {
108963  *             xtype: 'hiddenfield',
108964  *             name: 'hidden_field_1',
108965  *             value: 'value from hidden field'
108966  *         }],
108967  *
108968  *         buttons: [{
108969  *             text: 'Submit',
108970  *             handler: function() {
108971  *                 this.up('form').getForm().submit({
108972  *                     params: {
108973  *                         hidden_field_2: 'value from submit call'
108974  *                     }
108975  *                 });
108976  *             }
108977  *         }]
108978  *     });
108979  *
108980  * Submitting the above form will result in three values sent to the server:
108981  *
108982  *     text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
108983  *
108984  */
108985 Ext.define('Ext.form.field.Hidden', {
108986     extend:'Ext.form.field.Base',
108987     alias: ['widget.hiddenfield', 'widget.hidden'],
108988     alternateClassName: 'Ext.form.Hidden',
108989
108990     // private
108991     inputType : 'hidden',
108992     hideLabel: true,
108993     
108994     initComponent: function(){
108995         this.formItemCls += '-hidden';
108996         this.callParent();    
108997     },
108998     
108999     /**
109000      * @private
109001      * Override. Treat undefined and null values as equal to an empty string value.
109002      */
109003     isEqual: function(value1, value2) {
109004         return this.isEqualAsString(value1, value2);
109005     },
109006
109007     // These are all private overrides
109008     initEvents: Ext.emptyFn,
109009     setSize : Ext.emptyFn,
109010     setWidth : Ext.emptyFn,
109011     setHeight : Ext.emptyFn,
109012     setPosition : Ext.emptyFn,
109013     setPagePosition : Ext.emptyFn,
109014     markInvalid : Ext.emptyFn,
109015     clearInvalid : Ext.emptyFn
109016 });
109017
109018 /**
109019  * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
109020  * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
109021  *
109022  * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
109023  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
109024  *
109025  *     @example
109026  *     Ext.create('Ext.picker.Color', {
109027  *         value: '993300',  // initial selected color
109028  *         renderTo: Ext.getBody(),
109029  *         listeners: {
109030  *             select: function(picker, selColor) {
109031  *                 alert(selColor);
109032  *             }
109033  *         }
109034  *     });
109035  */
109036 Ext.define('Ext.picker.Color', {
109037     extend: 'Ext.Component',
109038     requires: 'Ext.XTemplate',
109039     alias: 'widget.colorpicker',
109040     alternateClassName: 'Ext.ColorPalette',
109041
109042     /**
109043      * @cfg {String} [componentCls='x-color-picker']
109044      * The CSS class to apply to the containing element.
109045      */
109046     componentCls : Ext.baseCSSPrefix + 'color-picker',
109047
109048     /**
109049      * @cfg {String} [selectedCls='x-color-picker-selected']
109050      * The CSS class to apply to the selected element
109051      */
109052     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
109053
109054     /**
109055      * @cfg {String} value
109056      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
109057      * codes are case-sensitive.
109058      */
109059     value : null,
109060
109061     /**
109062      * @cfg {String} clickEvent
109063      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
109064      */
109065     clickEvent :'click',
109066
109067     /**
109068      * @cfg {Boolean} allowReselect
109069      * If set to true then reselecting a color that is already selected fires the {@link #select} event
109070      */
109071     allowReselect : false,
109072
109073     /**
109074      * @property {String[]} colors
109075      * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
109076      * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
109077      * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
109078      * width setting until the box is symmetrical.
109079      *
109080      * You can override individual colors if needed:
109081      *
109082      *     var cp = new Ext.picker.Color();
109083      *     cp.colors[0] = 'FF0000';  // change the first box to red
109084      *
109085      * Or you can provide a custom array of your own for complete control:
109086      *
109087      *     var cp = new Ext.picker.Color();
109088      *     cp.colors = ['000000', '993300', '333300'];
109089      */
109090     colors : [
109091         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
109092         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
109093         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
109094         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
109095         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
109096     ],
109097
109098     /**
109099      * @cfg {Function} handler
109100      * A function that will handle the select event of this picker. The handler is passed the following parameters:
109101      *
109102      * - `picker` : ColorPicker
109103      *
109104      *   The {@link Ext.picker.Color picker}.
109105      *
109106      * - `color` : String
109107      *
109108      *   The 6-digit color hex code (without the # symbol).
109109      */
109110
109111     /**
109112      * @cfg {Object} scope
109113      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
109114      * Color picker instance.
109115      */
109116
109117     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
109118     
109119     renderTpl: [
109120         '<tpl for="colors">',
109121             '<a href="#" class="color-{.}" hidefocus="on">',
109122                 '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
109123             '</a>',
109124         '</tpl>'
109125     ],
109126
109127     // private
109128     initComponent : function(){
109129         var me = this;
109130
109131         me.callParent(arguments);
109132         me.addEvents(
109133             /**
109134              * @event select
109135              * Fires when a color is selected
109136              * @param {Ext.picker.Color} this
109137              * @param {String} color The 6-digit color hex code (without the # symbol)
109138              */
109139             'select'
109140         );
109141
109142         if (me.handler) {
109143             me.on('select', me.handler, me.scope, true);
109144         }
109145     },
109146
109147
109148     // private
109149     onRender : function(container, position){
109150         var me = this,
109151             clickEvent = me.clickEvent;
109152
109153         Ext.apply(me.renderData, {
109154             itemCls: me.itemCls,
109155             colors: me.colors
109156         });
109157         me.callParent(arguments);
109158
109159         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
109160         // always stop following the anchors
109161         if(clickEvent != 'click'){
109162             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
109163         }
109164     },
109165
109166     // private
109167     afterRender : function(){
109168         var me = this,
109169             value;
109170
109171         me.callParent(arguments);
109172         if (me.value) {
109173             value = me.value;
109174             me.value = null;
109175             me.select(value, true);
109176         }
109177     },
109178
109179     // private
109180     handleClick : function(event, target){
109181         var me = this,
109182             color;
109183
109184         event.stopEvent();
109185         if (!me.disabled) {
109186             color = target.className.match(me.colorRe)[1];
109187             me.select(color.toUpperCase());
109188         }
109189     },
109190
109191     /**
109192      * Selects the specified color in the picker (fires the {@link #select} event)
109193      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
109194      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
109195      */
109196     select : function(color, suppressEvent){
109197
109198         var me = this,
109199             selectedCls = me.selectedCls,
109200             value = me.value,
109201             el;
109202
109203         color = color.replace('#', '');
109204         if (!me.rendered) {
109205             me.value = color;
109206             return;
109207         }
109208
109209
109210         if (color != value || me.allowReselect) {
109211             el = me.el;
109212
109213             if (me.value) {
109214                 el.down('a.color-' + value).removeCls(selectedCls);
109215             }
109216             el.down('a.color-' + color).addCls(selectedCls);
109217             me.value = color;
109218             if (suppressEvent !== true) {
109219                 me.fireEvent('select', me, color);
109220             }
109221         }
109222     },
109223
109224     /**
109225      * Get the currently selected color value.
109226      * @return {String} value The selected value. Null if nothing is selected.
109227      */
109228     getValue: function(){
109229         return this.value || null;
109230     }
109231 });
109232
109233 /**
109234  * @private
109235  * @class Ext.layout.component.field.HtmlEditor
109236  * @extends Ext.layout.component.field.Field
109237  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
109238  * @private
109239  */
109240
109241 Ext.define('Ext.layout.component.field.HtmlEditor', {
109242     extend: 'Ext.layout.component.field.Field',
109243     alias: ['layout.htmleditor'],
109244
109245     type: 'htmleditor',
109246
109247     sizeBodyContents: function(width, height) {
109248         var me = this,
109249             owner = me.owner,
109250             bodyEl = owner.bodyEl,
109251             toolbar = owner.getToolbar(),
109252             textarea = owner.textareaEl,
109253             iframe = owner.iframeEl,
109254             editorHeight;
109255
109256         if (Ext.isNumber(width)) {
109257             width -= bodyEl.getFrameWidth('lr');
109258         }
109259         toolbar.setWidth(width);
109260         textarea.setWidth(width);
109261         iframe.setWidth(width);
109262
109263         // If fixed height, subtract toolbar height from the input area height
109264         if (Ext.isNumber(height)) {
109265             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
109266             textarea.setHeight(editorHeight);
109267             iframe.setHeight(editorHeight);
109268         }
109269     }
109270 });
109271 /**
109272  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
109273  * automatically hidden when needed. These are noted in the config options where appropriate.
109274  *
109275  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
109276  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
109277  * {@link Ext.tip.QuickTipManager#init initialized}.
109278  *
109279  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
109280  * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
109281  * default iframe reloading bugs.
109282  *
109283  * # Example usage
109284  *
109285  * Simple example rendered with default options:
109286  *
109287  *     @example
109288  *     Ext.tip.QuickTipManager.init();  // enable tooltips
109289  *     Ext.create('Ext.form.HtmlEditor', {
109290  *         width: 580,
109291  *         height: 250,
109292  *         renderTo: Ext.getBody()
109293  *     });
109294  *
109295  * Passed via xtype into a container and with custom options:
109296  *
109297  *     @example
109298  *     Ext.tip.QuickTipManager.init();  // enable tooltips
109299  *     new Ext.panel.Panel({
109300  *         title: 'HTML Editor',
109301  *         renderTo: Ext.getBody(),
109302  *         width: 550,
109303  *         height: 250,
109304  *         frame: true,
109305  *         layout: 'fit',
109306  *         items: {
109307  *             xtype: 'htmleditor',
109308  *             enableColors: false,
109309  *             enableAlignments: false
109310  *         }
109311  *     });
109312  */
109313 Ext.define('Ext.form.field.HtmlEditor', {
109314     extend:'Ext.Component',
109315     mixins: {
109316         labelable: 'Ext.form.Labelable',
109317         field: 'Ext.form.field.Field'
109318     },
109319     alias: 'widget.htmleditor',
109320     alternateClassName: 'Ext.form.HtmlEditor',
109321     requires: [
109322         'Ext.tip.QuickTipManager',
109323         'Ext.picker.Color',
109324         'Ext.toolbar.Item',
109325         'Ext.toolbar.Toolbar',
109326         'Ext.util.Format',
109327         'Ext.layout.component.field.HtmlEditor'
109328     ],
109329
109330     fieldSubTpl: [
109331         '<div id="{cmpId}-toolbarWrap" class="{toolbarWrapCls}"></div>',
109332         '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" class="{textareaCls}" ',
109333             'style="{size}" autocomplete="off"></textarea>',
109334         '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
109335         {
109336             compiled: true,
109337             disableFormats: true
109338         }
109339     ],
109340
109341     /**
109342      * @cfg {Boolean} enableFormat
109343      * Enable the bold, italic and underline buttons
109344      */
109345     enableFormat : true,
109346     /**
109347      * @cfg {Boolean} enableFontSize
109348      * Enable the increase/decrease font size buttons
109349      */
109350     enableFontSize : true,
109351     /**
109352      * @cfg {Boolean} enableColors
109353      * Enable the fore/highlight color buttons
109354      */
109355     enableColors : true,
109356     /**
109357      * @cfg {Boolean} enableAlignments
109358      * Enable the left, center, right alignment buttons
109359      */
109360     enableAlignments : true,
109361     /**
109362      * @cfg {Boolean} enableLists
109363      * Enable the bullet and numbered list buttons. Not available in Safari.
109364      */
109365     enableLists : true,
109366     /**
109367      * @cfg {Boolean} enableSourceEdit
109368      * Enable the switch to source edit button. Not available in Safari.
109369      */
109370     enableSourceEdit : true,
109371     /**
109372      * @cfg {Boolean} enableLinks
109373      * Enable the create link button. Not available in Safari.
109374      */
109375     enableLinks : true,
109376     /**
109377      * @cfg {Boolean} enableFont
109378      * Enable font selection. Not available in Safari.
109379      */
109380     enableFont : true,
109381     /**
109382      * @cfg {String} createLinkText
109383      * The default text for the create link prompt
109384      */
109385     createLinkText : 'Please enter the URL for the link:',
109386     /**
109387      * @cfg {String} [defaultLinkValue='http://']
109388      * The default value for the create link prompt
109389      */
109390     defaultLinkValue : 'http:/'+'/',
109391     /**
109392      * @cfg {String[]} fontFamilies
109393      * An array of available font families
109394      */
109395     fontFamilies : [
109396         'Arial',
109397         'Courier New',
109398         'Tahoma',
109399         'Times New Roman',
109400         'Verdana'
109401     ],
109402     defaultFont: 'tahoma',
109403     /**
109404      * @cfg {String} defaultValue
109405      * A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera
109406      * and IE6, â€‹(Zero-width space) in all other browsers).
109407      */
109408     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
109409
109410     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
109411
109412     componentLayout: 'htmleditor',
109413
109414     // private properties
109415     initialized : false,
109416     activated : false,
109417     sourceEditMode : false,
109418     iframePad:3,
109419     hideMode:'offsets',
109420
109421     maskOnDisable: true,
109422
109423     // private
109424     initComponent : function(){
109425         var me = this;
109426
109427         me.addEvents(
109428             /**
109429              * @event initialize
109430              * Fires when the editor is fully initialized (including the iframe)
109431              * @param {Ext.form.field.HtmlEditor} this
109432              */
109433             'initialize',
109434             /**
109435              * @event activate
109436              * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
109437              * @param {Ext.form.field.HtmlEditor} this
109438              */
109439             'activate',
109440              /**
109441              * @event beforesync
109442              * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
109443              * sync.
109444              * @param {Ext.form.field.HtmlEditor} this
109445              * @param {String} html
109446              */
109447             'beforesync',
109448              /**
109449              * @event beforepush
109450              * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
109451              * push.
109452              * @param {Ext.form.field.HtmlEditor} this
109453              * @param {String} html
109454              */
109455             'beforepush',
109456              /**
109457              * @event sync
109458              * Fires when the textarea is updated with content from the editor iframe.
109459              * @param {Ext.form.field.HtmlEditor} this
109460              * @param {String} html
109461              */
109462             'sync',
109463              /**
109464              * @event push
109465              * Fires when the iframe editor is updated with content from the textarea.
109466              * @param {Ext.form.field.HtmlEditor} this
109467              * @param {String} html
109468              */
109469             'push',
109470              /**
109471              * @event editmodechange
109472              * Fires when the editor switches edit modes
109473              * @param {Ext.form.field.HtmlEditor} this
109474              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
109475              */
109476             'editmodechange'
109477         );
109478
109479         me.callParent(arguments);
109480
109481         // Init mixins
109482         me.initLabelable();
109483         me.initField();
109484     },
109485
109486     /**
109487      * Called when the editor creates its toolbar. Override this method if you need to
109488      * add custom toolbar buttons.
109489      * @param {Ext.form.field.HtmlEditor} editor
109490      * @protected
109491      */
109492     createToolbar : function(editor){
109493         var me = this,
109494             items = [],
109495             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
109496             baseCSSPrefix = Ext.baseCSSPrefix,
109497             fontSelectItem, toolbar, undef;
109498
109499         function btn(id, toggle, handler){
109500             return {
109501                 itemId : id,
109502                 cls : baseCSSPrefix + 'btn-icon',
109503                 iconCls: baseCSSPrefix + 'edit-'+id,
109504                 enableToggle:toggle !== false,
109505                 scope: editor,
109506                 handler:handler||editor.relayBtnCmd,
109507                 clickEvent:'mousedown',
109508                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
109509                 overflowText: editor.buttonTips[id].title || undef,
109510                 tabIndex:-1
109511             };
109512         }
109513
109514
109515         if (me.enableFont && !Ext.isSafari2) {
109516             fontSelectItem = Ext.widget('component', {
109517                 renderTpl: [
109518                     '<select id="{id}-selectEl" class="{cls}">',
109519                         '<tpl for="fonts">',
109520                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
109521                         '</tpl>',
109522                     '</select>'
109523                 ],
109524                 renderData: {
109525                     cls: baseCSSPrefix + 'font-select',
109526                     fonts: me.fontFamilies,
109527                     defaultFont: me.defaultFont
109528                 },
109529                 childEls: ['selectEl'],
109530                 onDisable: function() {
109531                     var selectEl = this.selectEl;
109532                     if (selectEl) {
109533                         selectEl.dom.disabled = true;
109534                     }
109535                     Ext.Component.superclass.onDisable.apply(this, arguments);
109536                 },
109537                 onEnable: function() {
109538                     var selectEl = this.selectEl;
109539                     if (selectEl) {
109540                         selectEl.dom.disabled = false;
109541                     }
109542                     Ext.Component.superclass.onEnable.apply(this, arguments);
109543                 }
109544             });
109545
109546             items.push(
109547                 fontSelectItem,
109548                 '-'
109549             );
109550         }
109551
109552         if (me.enableFormat) {
109553             items.push(
109554                 btn('bold'),
109555                 btn('italic'),
109556                 btn('underline')
109557             );
109558         }
109559
109560         if (me.enableFontSize) {
109561             items.push(
109562                 '-',
109563                 btn('increasefontsize', false, me.adjustFont),
109564                 btn('decreasefontsize', false, me.adjustFont)
109565             );
109566         }
109567
109568         if (me.enableColors) {
109569             items.push(
109570                 '-', {
109571                     itemId: 'forecolor',
109572                     cls: baseCSSPrefix + 'btn-icon',
109573                     iconCls: baseCSSPrefix + 'edit-forecolor',
109574                     overflowText: editor.buttonTips.forecolor.title,
109575                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
109576                     tabIndex:-1,
109577                     menu : Ext.widget('menu', {
109578                         plain: true,
109579                         items: [{
109580                             xtype: 'colorpicker',
109581                             allowReselect: true,
109582                             focus: Ext.emptyFn,
109583                             value: '000000',
109584                             plain: true,
109585                             clickEvent: 'mousedown',
109586                             handler: function(cp, color) {
109587                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
109588                                 me.deferFocus();
109589                                 this.up('menu').hide();
109590                             }
109591                         }]
109592                     })
109593                 }, {
109594                     itemId: 'backcolor',
109595                     cls: baseCSSPrefix + 'btn-icon',
109596                     iconCls: baseCSSPrefix + 'edit-backcolor',
109597                     overflowText: editor.buttonTips.backcolor.title,
109598                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
109599                     tabIndex:-1,
109600                     menu : Ext.widget('menu', {
109601                         plain: true,
109602                         items: [{
109603                             xtype: 'colorpicker',
109604                             focus: Ext.emptyFn,
109605                             value: 'FFFFFF',
109606                             plain: true,
109607                             allowReselect: true,
109608                             clickEvent: 'mousedown',
109609                             handler: function(cp, color) {
109610                                 if (Ext.isGecko) {
109611                                     me.execCmd('useCSS', false);
109612                                     me.execCmd('hilitecolor', color);
109613                                     me.execCmd('useCSS', true);
109614                                     me.deferFocus();
109615                                 } else {
109616                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
109617                                     me.deferFocus();
109618                                 }
109619                                 this.up('menu').hide();
109620                             }
109621                         }]
109622                     })
109623                 }
109624             );
109625         }
109626
109627         if (me.enableAlignments) {
109628             items.push(
109629                 '-',
109630                 btn('justifyleft'),
109631                 btn('justifycenter'),
109632                 btn('justifyright')
109633             );
109634         }
109635
109636         if (!Ext.isSafari2) {
109637             if (me.enableLinks) {
109638                 items.push(
109639                     '-',
109640                     btn('createlink', false, me.createLink)
109641                 );
109642             }
109643
109644             if (me.enableLists) {
109645                 items.push(
109646                     '-',
109647                     btn('insertorderedlist'),
109648                     btn('insertunorderedlist')
109649                 );
109650             }
109651             if (me.enableSourceEdit) {
109652                 items.push(
109653                     '-',
109654                     btn('sourceedit', true, function(btn){
109655                         me.toggleSourceEdit(!me.sourceEditMode);
109656                     })
109657                 );
109658             }
109659         }
109660
109661         // build the toolbar
109662         toolbar = Ext.widget('toolbar', {
109663             renderTo: me.toolbarWrap,
109664             enableOverflow: true,
109665             items: items
109666         });
109667
109668         if (fontSelectItem) {
109669             me.fontSelect = fontSelectItem.selectEl;
109670
109671             me.mon(me.fontSelect, 'change', function(){
109672                 me.relayCmd('fontname', me.fontSelect.dom.value);
109673                 me.deferFocus();
109674             });
109675         }
109676
109677         // stop form submits
109678         me.mon(toolbar.el, 'click', function(e){
109679             e.preventDefault();
109680         });
109681
109682         me.toolbar = toolbar;
109683     },
109684
109685     onDisable: function() {
109686         this.bodyEl.mask();
109687         this.callParent(arguments);
109688     },
109689
109690     onEnable: function() {
109691         this.bodyEl.unmask();
109692         this.callParent(arguments);
109693     },
109694
109695     /**
109696      * Sets the read only state of this field.
109697      * @param {Boolean} readOnly Whether the field should be read only.
109698      */
109699     setReadOnly: function(readOnly) {
109700         var me = this,
109701             textareaEl = me.textareaEl,
109702             iframeEl = me.iframeEl,
109703             body;
109704
109705         me.readOnly = readOnly;
109706
109707         if (textareaEl) {
109708             textareaEl.dom.readOnly = readOnly;
109709         }
109710
109711         if (me.initialized) {
109712             body = me.getEditorBody();
109713             if (Ext.isIE) {
109714                 // Hide the iframe while setting contentEditable so it doesn't grab focus
109715                 iframeEl.setDisplayed(false);
109716                 body.contentEditable = !readOnly;
109717                 iframeEl.setDisplayed(true);
109718             } else {
109719                 me.setDesignMode(!readOnly);
109720             }
109721             if (body) {
109722                 body.style.cursor = readOnly ? 'default' : 'text';
109723             }
109724             me.disableItems(readOnly);
109725         }
109726     },
109727
109728     /**
109729      * Called when the editor initializes the iframe with HTML contents. Override this method if you
109730      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
109731      *
109732      * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
109733      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
109734      * Developer Tools to manually set the document mode, that will take precedence and override what this
109735      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
109736      * @protected
109737      */
109738     getDocMarkup: function() {
109739         var me = this,
109740             h = me.iframeEl.getHeight() - me.iframePad * 2;
109741         return Ext.String.format('<html><head><style type="text/css">body{border:0;margin:0;padding:{0}px;height:{1}px;box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;cursor:text}</style></head><body></body></html>', me.iframePad, h);
109742     },
109743
109744     // private
109745     getEditorBody: function() {
109746         var doc = this.getDoc();
109747         return doc.body || doc.documentElement;
109748     },
109749
109750     // private
109751     getDoc: function() {
109752         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
109753     },
109754
109755     // private
109756     getWin: function() {
109757         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
109758     },
109759
109760     // private
109761     onRender: function() {
109762         var me = this;
109763
109764         me.onLabelableRender();
109765
109766         me.addChildEls('toolbarWrap', 'iframeEl', 'textareaEl');
109767
109768         me.callParent(arguments);
109769
109770         me.textareaEl.dom.value = me.value || '';
109771
109772         // Start polling for when the iframe document is ready to be manipulated
109773         me.monitorTask = Ext.TaskManager.start({
109774             run: me.checkDesignMode,
109775             scope: me,
109776             interval:100
109777         });
109778
109779         me.createToolbar(me);
109780         me.disableItems(true);
109781     },
109782
109783     initRenderTpl: function() {
109784         var me = this;
109785         if (!me.hasOwnProperty('renderTpl')) {
109786             me.renderTpl = me.getTpl('labelableRenderTpl');
109787         }
109788         return me.callParent();
109789     },
109790
109791     initRenderData: function() {
109792         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
109793     },
109794
109795     getSubTplData: function() {
109796         var cssPrefix = Ext.baseCSSPrefix;
109797         return {
109798             cmpId: this.id,
109799             id: this.getInputId(),
109800             toolbarWrapCls: cssPrefix + 'html-editor-tb',
109801             textareaCls: cssPrefix + 'hidden',
109802             iframeName: Ext.id(),
109803             iframeSrc: Ext.SSL_SECURE_URL,
109804             size: 'height:100px;'
109805         };
109806     },
109807
109808     getSubTplMarkup: function() {
109809         var data = this.getSubTplData();
109810         return this.getTpl('fieldSubTpl').apply(data);
109811     },
109812
109813     getBodyNaturalWidth: function() {
109814         return 565;
109815     },
109816
109817     initFrameDoc: function() {
109818         var me = this,
109819             doc, task;
109820
109821         Ext.TaskManager.stop(me.monitorTask);
109822
109823         doc = me.getDoc();
109824         me.win = me.getWin();
109825
109826         doc.open();
109827         doc.write(me.getDocMarkup());
109828         doc.close();
109829
109830         task = { // must defer to wait for browser to be ready
109831             run: function() {
109832                 var doc = me.getDoc();
109833                 if (doc.body || doc.readyState === 'complete') {
109834                     Ext.TaskManager.stop(task);
109835                     me.setDesignMode(true);
109836                     Ext.defer(me.initEditor, 10, me);
109837                 }
109838             },
109839             interval : 10,
109840             duration:10000,
109841             scope: me
109842         };
109843         Ext.TaskManager.start(task);
109844     },
109845
109846     checkDesignMode: function() {
109847         var me = this,
109848             doc = me.getDoc();
109849         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
109850             me.initFrameDoc();
109851         }
109852     },
109853
109854     /**
109855      * @private
109856      * Sets current design mode. To enable, mode can be true or 'on', off otherwise
109857      */
109858     setDesignMode: function(mode) {
109859         var me = this,
109860             doc = me.getDoc();
109861         if (doc) {
109862             if (me.readOnly) {
109863                 mode = false;
109864             }
109865             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
109866         }
109867     },
109868
109869     // private
109870     getDesignMode: function() {
109871         var doc = this.getDoc();
109872         return !doc ? '' : String(doc.designMode).toLowerCase();
109873     },
109874
109875     disableItems: function(disabled) {
109876         this.getToolbar().items.each(function(item){
109877             if(item.getItemId() !== 'sourceedit'){
109878                 item.setDisabled(disabled);
109879             }
109880         });
109881     },
109882
109883     /**
109884      * Toggles the editor between standard and source edit mode.
109885      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
109886      */
109887     toggleSourceEdit: function(sourceEditMode) {
109888         var me = this,
109889             iframe = me.iframeEl,
109890             textarea = me.textareaEl,
109891             hiddenCls = Ext.baseCSSPrefix + 'hidden',
109892             btn = me.getToolbar().getComponent('sourceedit');
109893
109894         if (!Ext.isBoolean(sourceEditMode)) {
109895             sourceEditMode = !me.sourceEditMode;
109896         }
109897         me.sourceEditMode = sourceEditMode;
109898
109899         if (btn.pressed !== sourceEditMode) {
109900             btn.toggle(sourceEditMode);
109901         }
109902         if (sourceEditMode) {
109903             me.disableItems(true);
109904             me.syncValue();
109905             iframe.addCls(hiddenCls);
109906             textarea.removeCls(hiddenCls);
109907             textarea.dom.removeAttribute('tabIndex');
109908             textarea.focus();
109909         }
109910         else {
109911             if (me.initialized) {
109912                 me.disableItems(me.readOnly);
109913             }
109914             me.pushValue();
109915             iframe.removeCls(hiddenCls);
109916             textarea.addCls(hiddenCls);
109917             textarea.dom.setAttribute('tabIndex', -1);
109918             me.deferFocus();
109919         }
109920         me.fireEvent('editmodechange', me, sourceEditMode);
109921         me.doComponentLayout();
109922     },
109923
109924     // private used internally
109925     createLink : function() {
109926         var url = prompt(this.createLinkText, this.defaultLinkValue);
109927         if (url && url !== 'http:/'+'/') {
109928             this.relayCmd('createlink', url);
109929         }
109930     },
109931
109932     clearInvalid: Ext.emptyFn,
109933
109934     // docs inherit from Field
109935     setValue: function(value) {
109936         var me = this,
109937             textarea = me.textareaEl;
109938         me.mixins.field.setValue.call(me, value);
109939         if (value === null || value === undefined) {
109940             value = '';
109941         }
109942         if (textarea) {
109943             textarea.dom.value = value;
109944         }
109945         me.pushValue();
109946         return this;
109947     },
109948
109949     /**
109950      * If you need/want custom HTML cleanup, this is the method you should override.
109951      * @param {String} html The HTML to be cleaned
109952      * @return {String} The cleaned HTML
109953      * @protected
109954      */
109955     cleanHtml: function(html) {
109956         html = String(html);
109957         if (Ext.isWebKit) { // strip safari nonsense
109958             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
109959         }
109960
109961         /*
109962          * Neat little hack. Strips out all the non-digit characters from the default
109963          * value and compares it to the character code of the first character in the string
109964          * because it can cause encoding issues when posted to the server.
109965          */
109966         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
109967             html = html.substring(1);
109968         }
109969         return html;
109970     },
109971
109972     /**
109973      * Syncs the contents of the editor iframe with the textarea.
109974      * @protected
109975      */
109976     syncValue : function(){
109977         var me = this,
109978             body, html, bodyStyle, match;
109979         if (me.initialized) {
109980             body = me.getEditorBody();
109981             html = body.innerHTML;
109982             if (Ext.isWebKit) {
109983                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
109984                 match = bodyStyle.match(/text-align:(.*?);/i);
109985                 if (match && match[1]) {
109986                     html = '<div style="' + match[0] + '">' + html + '</div>';
109987                 }
109988             }
109989             html = me.cleanHtml(html);
109990             if (me.fireEvent('beforesync', me, html) !== false) {
109991                 me.textareaEl.dom.value = html;
109992                 me.fireEvent('sync', me, html);
109993             }
109994         }
109995     },
109996
109997     //docs inherit from Field
109998     getValue : function() {
109999         var me = this,
110000             value;
110001         if (!me.sourceEditMode) {
110002             me.syncValue();
110003         }
110004         value = me.rendered ? me.textareaEl.dom.value : me.value;
110005         me.value = value;
110006         return value;
110007     },
110008
110009     /**
110010      * Pushes the value of the textarea into the iframe editor.
110011      * @protected
110012      */
110013     pushValue: function() {
110014         var me = this,
110015             v;
110016         if(me.initialized){
110017             v = me.textareaEl.dom.value || '';
110018             if (!me.activated && v.length < 1) {
110019                 v = me.defaultValue;
110020             }
110021             if (me.fireEvent('beforepush', me, v) !== false) {
110022                 me.getEditorBody().innerHTML = v;
110023                 if (Ext.isGecko) {
110024                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
110025                     me.setDesignMode(false);  //toggle off first
110026                     me.setDesignMode(true);
110027                 }
110028                 me.fireEvent('push', me, v);
110029             }
110030         }
110031     },
110032
110033     // private
110034     deferFocus : function(){
110035          this.focus(false, true);
110036     },
110037
110038     getFocusEl: function() {
110039         var me = this,
110040             win = me.win;
110041         return win && !me.sourceEditMode ? win : me.textareaEl;
110042     },
110043
110044     // private
110045     initEditor : function(){
110046         //Destroying the component during/before initEditor can cause issues.
110047         try {
110048             var me = this,
110049                 dbody = me.getEditorBody(),
110050                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
110051                 doc,
110052                 fn;
110053
110054             ss['background-attachment'] = 'fixed'; // w3c
110055             dbody.bgProperties = 'fixed'; // ie
110056
110057             Ext.DomHelper.applyStyles(dbody, ss);
110058
110059             doc = me.getDoc();
110060
110061             if (doc) {
110062                 try {
110063                     Ext.EventManager.removeAll(doc);
110064                 } catch(e) {}
110065             }
110066
110067             /*
110068              * We need to use createDelegate here, because when using buffer, the delayed task is added
110069              * as a property to the function. When the listener is removed, the task is deleted from the function.
110070              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
110071              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
110072              */
110073             fn = Ext.Function.bind(me.onEditorEvent, me);
110074             Ext.EventManager.on(doc, {
110075                 mousedown: fn,
110076                 dblclick: fn,
110077                 click: fn,
110078                 keyup: fn,
110079                 buffer:100
110080             });
110081
110082             // These events need to be relayed from the inner document (where they stop
110083             // bubbling) up to the outer document. This has to be done at the DOM level so
110084             // the event reaches listeners on elements like the document body. The effected
110085             // mechanisms that depend on this bubbling behavior are listed to the right
110086             // of the event.
110087             fn = me.onRelayedEvent;
110088             Ext.EventManager.on(doc, {
110089                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
110090                 mousemove: fn, // window resize drag detection
110091                 mouseup: fn,   // window resize termination
110092                 click: fn,     // not sure, but just to be safe
110093                 dblclick: fn,  // not sure again
110094                 scope: me
110095             });
110096
110097             if (Ext.isGecko) {
110098                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
110099             }
110100             if (me.fixKeys) {
110101                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
110102             }
110103
110104             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
110105             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
110106             doc.editorInitialized = true;
110107
110108             me.initialized = true;
110109             me.pushValue();
110110             me.setReadOnly(me.readOnly);
110111             me.fireEvent('initialize', me);
110112         } catch(ex) {
110113             // ignore (why?)
110114         }
110115     },
110116
110117     // private
110118     beforeDestroy : function(){
110119         var me = this,
110120             monitorTask = me.monitorTask,
110121             doc, prop;
110122
110123         if (monitorTask) {
110124             Ext.TaskManager.stop(monitorTask);
110125         }
110126         if (me.rendered) {
110127             try {
110128                 doc = me.getDoc();
110129                 if (doc) {
110130                     Ext.EventManager.removeAll(doc);
110131                     for (prop in doc) {
110132                         if (doc.hasOwnProperty(prop)) {
110133                             delete doc[prop];
110134                         }
110135                     }
110136                 }
110137             } catch(e) {
110138                 // ignore (why?)
110139             }
110140             Ext.destroyMembers(me, 'tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
110141         }
110142         me.callParent();
110143     },
110144
110145     // private
110146     onRelayedEvent: function (event) {
110147         // relay event from the iframe's document to the document that owns the iframe...
110148
110149         var iframeEl = this.iframeEl,
110150             iframeXY = iframeEl.getXY(),
110151             eventXY = event.getXY();
110152
110153         // the event from the inner document has XY relative to that document's origin,
110154         // so adjust it to use the origin of the iframe in the outer document:
110155         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
110156
110157         event.injectEvent(iframeEl); // blame the iframe for the event...
110158
110159         event.xy = eventXY; // restore the original XY (just for safety)
110160     },
110161
110162     // private
110163     onFirstFocus : function(){
110164         var me = this,
110165             selection, range;
110166         me.activated = true;
110167         me.disableItems(me.readOnly);
110168         if (Ext.isGecko) { // prevent silly gecko errors
110169             me.win.focus();
110170             selection = me.win.getSelection();
110171             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
110172                 range = selection.getRangeAt(0);
110173                 range.selectNodeContents(me.getEditorBody());
110174                 range.collapse(true);
110175                 me.deferFocus();
110176             }
110177             try {
110178                 me.execCmd('useCSS', true);
110179                 me.execCmd('styleWithCSS', false);
110180             } catch(e) {
110181                 // ignore (why?)
110182             }
110183         }
110184         me.fireEvent('activate', me);
110185     },
110186
110187     // private
110188     adjustFont: function(btn) {
110189         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
110190             size = this.getDoc().queryCommandValue('FontSize') || '2',
110191             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
110192             isSafari;
110193         size = parseInt(size, 10);
110194         if (isPxSize) {
110195             // Safari 3 values
110196             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
110197             if (size <= 10) {
110198                 size = 1 + adjust;
110199             }
110200             else if (size <= 13) {
110201                 size = 2 + adjust;
110202             }
110203             else if (size <= 16) {
110204                 size = 3 + adjust;
110205             }
110206             else if (size <= 18) {
110207                 size = 4 + adjust;
110208             }
110209             else if (size <= 24) {
110210                 size = 5 + adjust;
110211             }
110212             else {
110213                 size = 6 + adjust;
110214             }
110215             size = Ext.Number.constrain(size, 1, 6);
110216         } else {
110217             isSafari = Ext.isSafari;
110218             if (isSafari) { // safari
110219                 adjust *= 2;
110220             }
110221             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
110222         }
110223         this.execCmd('FontSize', size);
110224     },
110225
110226     // private
110227     onEditorEvent: function(e) {
110228         this.updateToolbar();
110229     },
110230
110231     /**
110232      * Triggers a toolbar update by reading the markup state of the current selection in the editor.
110233      * @protected
110234      */
110235     updateToolbar: function() {
110236         var me = this,
110237             btns, doc, name, fontSelect;
110238
110239         if (me.readOnly) {
110240             return;
110241         }
110242
110243         if (!me.activated) {
110244             me.onFirstFocus();
110245             return;
110246         }
110247
110248         btns = me.getToolbar().items.map;
110249         doc = me.getDoc();
110250
110251         if (me.enableFont && !Ext.isSafari2) {
110252             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
110253             fontSelect = me.fontSelect.dom;
110254             if (name !== fontSelect.value) {
110255                 fontSelect.value = name;
110256             }
110257         }
110258
110259         function updateButtons() {
110260             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
110261                 btns[name].toggle(doc.queryCommandState(name));
110262             });
110263         }
110264         if(me.enableFormat){
110265             updateButtons('bold', 'italic', 'underline');
110266         }
110267         if(me.enableAlignments){
110268             updateButtons('justifyleft', 'justifycenter', 'justifyright');
110269         }
110270         if(!Ext.isSafari2 && me.enableLists){
110271             updateButtons('insertorderedlist', 'insertunorderedlist');
110272         }
110273
110274         Ext.menu.Manager.hideAll();
110275
110276         me.syncValue();
110277     },
110278
110279     // private
110280     relayBtnCmd: function(btn) {
110281         this.relayCmd(btn.getItemId());
110282     },
110283
110284     /**
110285      * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
110286      * **This should only be called after the editor is initialized.**
110287      * @param {String} cmd The Midas command
110288      * @param {String/Boolean} [value=null] The value to pass to the command
110289      */
110290     relayCmd: function(cmd, value) {
110291         Ext.defer(function() {
110292             var me = this;
110293             me.focus();
110294             me.execCmd(cmd, value);
110295             me.updateToolbar();
110296         }, 10, this);
110297     },
110298
110299     /**
110300      * Executes a Midas editor command directly on the editor document. For visual commands, you should use
110301      * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
110302      * @param {String} cmd The Midas command
110303      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
110304      */
110305     execCmd : function(cmd, value){
110306         var me = this,
110307             doc = me.getDoc(),
110308             undef;
110309         doc.execCommand(cmd, false, value === undef ? null : value);
110310         me.syncValue();
110311     },
110312
110313     // private
110314     applyCommand : function(e){
110315         if (e.ctrlKey) {
110316             var me = this,
110317                 c = e.getCharCode(), cmd;
110318             if (c > 0) {
110319                 c = String.fromCharCode(c);
110320                 switch (c) {
110321                     case 'b':
110322                         cmd = 'bold';
110323                     break;
110324                     case 'i':
110325                         cmd = 'italic';
110326                     break;
110327                     case 'u':
110328                         cmd = 'underline';
110329                     break;
110330                 }
110331                 if (cmd) {
110332                     me.win.focus();
110333                     me.execCmd(cmd);
110334                     me.deferFocus();
110335                     e.preventDefault();
110336                 }
110337             }
110338         }
110339     },
110340
110341     /**
110342      * Inserts the passed text at the current cursor position.
110343      * Note: the editor must be initialized and activated to insert text.
110344      * @param {String} text
110345      */
110346     insertAtCursor : function(text){
110347         var me = this,
110348             range;
110349
110350         if (me.activated) {
110351             me.win.focus();
110352             if (Ext.isIE) {
110353                 range = me.getDoc().selection.createRange();
110354                 if (range) {
110355                     range.pasteHTML(text);
110356                     me.syncValue();
110357                     me.deferFocus();
110358                 }
110359             }else{
110360                 me.execCmd('InsertHTML', text);
110361                 me.deferFocus();
110362             }
110363         }
110364     },
110365
110366     // private
110367     fixKeys: function() { // load time branching for fastest keydown performance
110368         if (Ext.isIE) {
110369             return function(e){
110370                 var me = this,
110371                     k = e.getKey(),
110372                     doc = me.getDoc(),
110373                     range, target;
110374                 if (k === e.TAB) {
110375                     e.stopEvent();
110376                     range = doc.selection.createRange();
110377                     if(range){
110378                         range.collapse(true);
110379                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
110380                         me.deferFocus();
110381                     }
110382                 }
110383                 else if (k === e.ENTER) {
110384                     range = doc.selection.createRange();
110385                     if (range) {
110386                         target = range.parentElement();
110387                         if(!target || target.tagName.toLowerCase() !== 'li'){
110388                             e.stopEvent();
110389                             range.pasteHTML('<br />');
110390                             range.collapse(false);
110391                             range.select();
110392                         }
110393                     }
110394                 }
110395             };
110396         }
110397
110398         if (Ext.isOpera) {
110399             return function(e){
110400                 var me = this;
110401                 if (e.getKey() === e.TAB) {
110402                     e.stopEvent();
110403                     me.win.focus();
110404                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
110405                     me.deferFocus();
110406                 }
110407             };
110408         }
110409
110410         if (Ext.isWebKit) {
110411             return function(e){
110412                 var me = this,
110413                     k = e.getKey();
110414                 if (k === e.TAB) {
110415                     e.stopEvent();
110416                     me.execCmd('InsertText','\t');
110417                     me.deferFocus();
110418                 }
110419                 else if (k === e.ENTER) {
110420                     e.stopEvent();
110421                     me.execCmd('InsertHtml','<br /><br />');
110422                     me.deferFocus();
110423                 }
110424             };
110425         }
110426
110427         return null; // not needed, so null
110428     }(),
110429
110430     /**
110431      * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
110432      * @return {Ext.toolbar.Toolbar}
110433      */
110434     getToolbar : function(){
110435         return this.toolbar;
110436     },
110437
110438     /**
110439      * @property {Object} buttonTips
110440      * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
110441      * that button and the value is a valid QuickTips object. For example:
110442      *
110443      *     {
110444      *         bold : {
110445      *             title: 'Bold (Ctrl+B)',
110446      *             text: 'Make the selected text bold.',
110447      *             cls: 'x-html-editor-tip'
110448      *         },
110449      *         italic : {
110450      *             title: 'Italic (Ctrl+I)',
110451      *             text: 'Make the selected text italic.',
110452      *             cls: 'x-html-editor-tip'
110453      *         },
110454      *         ...
110455      */
110456     buttonTips : {
110457         bold : {
110458             title: 'Bold (Ctrl+B)',
110459             text: 'Make the selected text bold.',
110460             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110461         },
110462         italic : {
110463             title: 'Italic (Ctrl+I)',
110464             text: 'Make the selected text italic.',
110465             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110466         },
110467         underline : {
110468             title: 'Underline (Ctrl+U)',
110469             text: 'Underline the selected text.',
110470             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110471         },
110472         increasefontsize : {
110473             title: 'Grow Text',
110474             text: 'Increase the font size.',
110475             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110476         },
110477         decreasefontsize : {
110478             title: 'Shrink Text',
110479             text: 'Decrease the font size.',
110480             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110481         },
110482         backcolor : {
110483             title: 'Text Highlight Color',
110484             text: 'Change the background color of the selected text.',
110485             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110486         },
110487         forecolor : {
110488             title: 'Font Color',
110489             text: 'Change the color of the selected text.',
110490             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110491         },
110492         justifyleft : {
110493             title: 'Align Text Left',
110494             text: 'Align text to the left.',
110495             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110496         },
110497         justifycenter : {
110498             title: 'Center Text',
110499             text: 'Center text in the editor.',
110500             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110501         },
110502         justifyright : {
110503             title: 'Align Text Right',
110504             text: 'Align text to the right.',
110505             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110506         },
110507         insertunorderedlist : {
110508             title: 'Bullet List',
110509             text: 'Start a bulleted list.',
110510             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110511         },
110512         insertorderedlist : {
110513             title: 'Numbered List',
110514             text: 'Start a numbered list.',
110515             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110516         },
110517         createlink : {
110518             title: 'Hyperlink',
110519             text: 'Make the selected text a hyperlink.',
110520             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110521         },
110522         sourceedit : {
110523             title: 'Source Edit',
110524             text: 'Switch to source editing mode.',
110525             cls: Ext.baseCSSPrefix + 'html-editor-tip'
110526         }
110527     }
110528
110529     // hide stuff that is not compatible
110530     /**
110531      * @event blur
110532      * @hide
110533      */
110534     /**
110535      * @event change
110536      * @hide
110537      */
110538     /**
110539      * @event focus
110540      * @hide
110541      */
110542     /**
110543      * @event specialkey
110544      * @hide
110545      */
110546     /**
110547      * @cfg {String} fieldCls @hide
110548      */
110549     /**
110550      * @cfg {String} focusCls @hide
110551      */
110552     /**
110553      * @cfg {String} autoCreate @hide
110554      */
110555     /**
110556      * @cfg {String} inputType @hide
110557      */
110558     /**
110559      * @cfg {String} invalidCls @hide
110560      */
110561     /**
110562      * @cfg {String} invalidText @hide
110563      */
110564     /**
110565      * @cfg {String} msgFx @hide
110566      */
110567     /**
110568      * @cfg {Boolean} allowDomMove @hide
110569      */
110570     /**
110571      * @cfg {String} applyTo @hide
110572      */
110573     /**
110574      * @cfg {String} readOnly  @hide
110575      */
110576     /**
110577      * @cfg {String} tabIndex  @hide
110578      */
110579     /**
110580      * @method validate
110581      * @hide
110582      */
110583 });
110584
110585 /**
110586  * @docauthor Robert Dougan <rob@sencha.com>
110587  *
110588  * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
110589  * at a time within a group of radios with the same name.
110590  *
110591  * # Labeling
110592  *
110593  * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
110594  * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
110595  * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
110596  *
110597  * # Values
110598  *
110599  * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
110600  *
110601  * The following values will check the radio:
110602  *
110603  * - `true`
110604  * - `'true'`
110605  * - `'1'`
110606  * - `'on'`
110607  *
110608  * Any other value will uncheck it.
110609  *
110610  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
110611  * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
110612  * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
110613  *
110614  * # Example usage
110615  *
110616  *     @example
110617  *     Ext.create('Ext.form.Panel', {
110618  *         title      : 'Order Form',
110619  *         width      : 300,
110620  *         bodyPadding: 10,
110621  *         renderTo   : Ext.getBody(),
110622  *         items: [
110623  *             {
110624  *                 xtype      : 'fieldcontainer',
110625  *                 fieldLabel : 'Size',
110626  *                 defaultType: 'radiofield',
110627  *                 defaults: {
110628  *                     flex: 1
110629  *                 },
110630  *                 layout: 'hbox',
110631  *                 items: [
110632  *                     {
110633  *                         boxLabel  : 'M',
110634  *                         name      : 'size',
110635  *                         inputValue: 'm',
110636  *                         id        : 'radio1'
110637  *                     }, {
110638  *                         boxLabel  : 'L',
110639  *                         name      : 'size',
110640  *                         inputValue: 'l',
110641  *                         id        : 'radio2'
110642  *                     }, {
110643  *                         boxLabel  : 'XL',
110644  *                         name      : 'size',
110645  *                         inputValue: 'xl',
110646  *                         id        : 'radio3'
110647  *                     }
110648  *                 ]
110649  *             },
110650  *             {
110651  *                 xtype      : 'fieldcontainer',
110652  *                 fieldLabel : 'Color',
110653  *                 defaultType: 'radiofield',
110654  *                 defaults: {
110655  *                     flex: 1
110656  *                 },
110657  *                 layout: 'hbox',
110658  *                 items: [
110659  *                     {
110660  *                         boxLabel  : 'Blue',
110661  *                         name      : 'color',
110662  *                         inputValue: 'blue',
110663  *                         id        : 'radio4'
110664  *                     }, {
110665  *                         boxLabel  : 'Grey',
110666  *                         name      : 'color',
110667  *                         inputValue: 'grey',
110668  *                         id        : 'radio5'
110669  *                     }, {
110670  *                         boxLabel  : 'Black',
110671  *                         name      : 'color',
110672  *                         inputValue: 'black',
110673  *                         id        : 'radio6'
110674  *                     }
110675  *                 ]
110676  *             }
110677  *         ],
110678  *         bbar: [
110679  *             {
110680  *                 text: 'Smaller Size',
110681  *                 handler: function() {
110682  *                     var radio1 = Ext.getCmp('radio1'),
110683  *                         radio2 = Ext.getCmp('radio2'),
110684  *                         radio3 = Ext.getCmp('radio3');
110685  *
110686  *                     //if L is selected, change to M
110687  *                     if (radio2.getValue()) {
110688  *                         radio1.setValue(true);
110689  *                         return;
110690  *                     }
110691  *
110692  *                     //if XL is selected, change to L
110693  *                     if (radio3.getValue()) {
110694  *                         radio2.setValue(true);
110695  *                         return;
110696  *                     }
110697  *
110698  *                     //if nothing is set, set size to S
110699  *                     radio1.setValue(true);
110700  *                 }
110701  *             },
110702  *             {
110703  *                 text: 'Larger Size',
110704  *                 handler: function() {
110705  *                     var radio1 = Ext.getCmp('radio1'),
110706  *                         radio2 = Ext.getCmp('radio2'),
110707  *                         radio3 = Ext.getCmp('radio3');
110708  *
110709  *                     //if M is selected, change to L
110710  *                     if (radio1.getValue()) {
110711  *                         radio2.setValue(true);
110712  *                         return;
110713  *                     }
110714  *
110715  *                     //if L is selected, change to XL
110716  *                     if (radio2.getValue()) {
110717  *                         radio3.setValue(true);
110718  *                         return;
110719  *                     }
110720  *
110721  *                     //if nothing is set, set size to XL
110722  *                     radio3.setValue(true);
110723  *                 }
110724  *             },
110725  *             '-',
110726  *             {
110727  *                 text: 'Select color',
110728  *                 menu: {
110729  *                     indent: false,
110730  *                     items: [
110731  *                         {
110732  *                             text: 'Blue',
110733  *                             handler: function() {
110734  *                                 var radio = Ext.getCmp('radio4');
110735  *                                 radio.setValue(true);
110736  *                             }
110737  *                         },
110738  *                         {
110739  *                             text: 'Grey',
110740  *                             handler: function() {
110741  *                                 var radio = Ext.getCmp('radio5');
110742  *                                 radio.setValue(true);
110743  *                             }
110744  *                         },
110745  *                         {
110746  *                             text: 'Black',
110747  *                             handler: function() {
110748  *                                 var radio = Ext.getCmp('radio6');
110749  *                                 radio.setValue(true);
110750  *                             }
110751  *                         }
110752  *                     ]
110753  *                 }
110754  *             }
110755  *         ]
110756  *     });
110757  */
110758 Ext.define('Ext.form.field.Radio', {
110759     extend:'Ext.form.field.Checkbox',
110760     alias: ['widget.radiofield', 'widget.radio'],
110761     alternateClassName: 'Ext.form.Radio',
110762     requires: ['Ext.form.RadioManager'],
110763
110764     isRadio: true,
110765
110766     /**
110767      * @cfg {String} uncheckedValue @hide
110768      */
110769
110770     // private
110771     inputType: 'radio',
110772     ariaRole: 'radio',
110773
110774     /**
110775      * If this radio is part of a group, it will return the selected value
110776      * @return {String}
110777      */
110778     getGroupValue: function() {
110779         var selected = this.getManager().getChecked(this.name);
110780         return selected ? selected.inputValue : null;
110781     },
110782
110783     /**
110784      * @private Handle click on the radio button
110785      */
110786     onBoxClick: function(e) {
110787         var me = this;
110788         if (!me.disabled && !me.readOnly) {
110789             this.setValue(true);
110790         }
110791     },
110792
110793     /**
110794      * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
110795      * of the same name whose value is the value specified.
110796      * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
110797      * @return {Ext.form.field.Radio} this
110798      */
110799     setValue: function(v) {
110800         var me = this,
110801             active;
110802
110803         if (Ext.isBoolean(v)) {
110804             me.callParent(arguments);
110805         } else {
110806             active = me.getManager().getWithValue(me.name, v).getAt(0);
110807             if (active) {
110808                 active.setValue(true);
110809             }
110810         }
110811         return me;
110812     },
110813
110814     /**
110815      * Returns the submit value for the checkbox which can be used when submitting forms.
110816      * @return {Boolean/Object} True if checked, null if not.
110817      */
110818     getSubmitValue: function() {
110819         return this.checked ? this.inputValue : null;
110820     },
110821
110822     getModelData: function() {
110823         return this.getSubmitData();
110824     },
110825
110826     // inherit docs
110827     onChange: function(newVal, oldVal) {
110828         var me = this;
110829         me.callParent(arguments);
110830
110831         if (newVal) {
110832             this.getManager().getByName(me.name).each(function(item){
110833                 if (item !== me) {
110834                     item.setValue(false);
110835                 }
110836             }, me);
110837         }
110838     },
110839
110840     // inherit docs
110841     getManager: function() {
110842         return Ext.form.RadioManager;
110843     }
110844 });
110845
110846 /**
110847  * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
110848  * class to allow browsing and selection of valid times, but could also be used with other components.
110849  *
110850  * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
110851  * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
110852  * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
110853  * config.
110854  *
110855  * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
110856  *
110857  *     @example
110858  *     Ext.create('Ext.picker.Time', {
110859  *        width: 60,
110860  *        minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
110861  *        maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
110862  *        renderTo: Ext.getBody()
110863  *     });
110864  */
110865 Ext.define('Ext.picker.Time', {
110866     extend: 'Ext.view.BoundList',
110867     alias: 'widget.timepicker',
110868     requires: ['Ext.data.Store', 'Ext.Date'],
110869
110870     /**
110871      * @cfg {Date} minValue
110872      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
110873      * used); no parsing of String values will be done.
110874      */
110875
110876     /**
110877      * @cfg {Date} maxValue
110878      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
110879      * used); no parsing of String values will be done.
110880      */
110881
110882     /**
110883      * @cfg {Number} increment
110884      * The number of minutes between each time value in the list.
110885      */
110886     increment: 15,
110887
110888     /**
110889      * @cfg {String} format
110890      * The default time format string which can be overriden for localization support. The format must be valid
110891      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110892      * instead.
110893      */
110894     format : "g:i A",
110895
110896     /**
110897      * @hide
110898      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
110899      * an internal field name only and is not useful to change via config.
110900      */
110901     displayField: 'disp',
110902
110903     /**
110904      * @private
110905      * Year, month, and day that all times will be normalized into internally.
110906      */
110907     initDate: [2008,0,1],
110908
110909     componentCls: Ext.baseCSSPrefix + 'timepicker',
110910
110911     /**
110912      * @hide
110913      */
110914     loadMask: false,
110915
110916     initComponent: function() {
110917         var me = this,
110918             dateUtil = Ext.Date,
110919             clearTime = dateUtil.clearTime,
110920             initDate = me.initDate;
110921
110922         // Set up absolute min and max for the entire day
110923         me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
110924         me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
110925
110926         me.store = me.createStore();
110927         me.updateList();
110928
110929         me.callParent();
110930     },
110931
110932     /**
110933      * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
110934      * fields will be used); no parsing of String values will be done.
110935      * @param {Date} value
110936      */
110937     setMinValue: function(value) {
110938         this.minValue = value;
110939         this.updateList();
110940     },
110941
110942     /**
110943      * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
110944      * fields will be used); no parsing of String values will be done.
110945      * @param {Date} value
110946      */
110947     setMaxValue: function(value) {
110948         this.maxValue = value;
110949         this.updateList();
110950     },
110951
110952     /**
110953      * @private
110954      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
110955      * the time fields are significant. This makes values suitable for time comparison.
110956      * @param {Date} date
110957      */
110958     normalizeDate: function(date) {
110959         var initDate = this.initDate;
110960         date.setFullYear(initDate[0], initDate[1], initDate[2]);
110961         return date;
110962     },
110963
110964     /**
110965      * Update the list of available times in the list to be constrained within the {@link #minValue}
110966      * and {@link #maxValue}.
110967      */
110968     updateList: function() {
110969         var me = this,
110970             min = me.normalizeDate(me.minValue || me.absMin),
110971             max = me.normalizeDate(me.maxValue || me.absMax);
110972
110973         me.store.filterBy(function(record) {
110974             var date = record.get('date');
110975             return date >= min && date <= max;
110976         });
110977     },
110978
110979     /**
110980      * @private
110981      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
110982      * is loaded with all possible times, and it is later filtered to hide those times outside
110983      * the minValue/maxValue.
110984      */
110985     createStore: function() {
110986         var me = this,
110987             utilDate = Ext.Date,
110988             times = [],
110989             min = me.absMin,
110990             max = me.absMax;
110991
110992         while(min <= max){
110993             times.push({
110994                 disp: utilDate.dateFormat(min, me.format),
110995                 date: min
110996             });
110997             min = utilDate.add(min, 'mi', me.increment);
110998         }
110999
111000         return Ext.create('Ext.data.Store', {
111001             fields: ['disp', 'date'],
111002             data: times
111003         });
111004     }
111005
111006 });
111007
111008 /**
111009  * Provides a time input field with a time dropdown and automatic time validation.
111010  *
111011  * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
111012  * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
111013  * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
111014  * the user's locale.
111015  *
111016  * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
111017  * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
111018  *
111019  * Example usage:
111020  *
111021  *     @example
111022  *     Ext.create('Ext.form.Panel', {
111023  *         title: 'Time Card',
111024  *         width: 300,
111025  *         bodyPadding: 10,
111026  *         renderTo: Ext.getBody(),
111027  *         items: [{
111028  *             xtype: 'timefield',
111029  *             name: 'in',
111030  *             fieldLabel: 'Time In',
111031  *             minValue: '6:00 AM',
111032  *             maxValue: '8:00 PM',
111033  *             increment: 30,
111034  *             anchor: '100%'
111035  *         }, {
111036  *             xtype: 'timefield',
111037  *             name: 'out',
111038  *             fieldLabel: 'Time Out',
111039  *             minValue: '6:00 AM',
111040  *             maxValue: '8:00 PM',
111041  *             increment: 30,
111042  *             anchor: '100%'
111043  *        }]
111044  *     });
111045  */
111046 Ext.define('Ext.form.field.Time', {
111047     extend:'Ext.form.field.Picker',
111048     alias: 'widget.timefield',
111049     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
111050     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
111051
111052     /**
111053      * @cfg {String} triggerCls
111054      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
111055      * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field
111056      * trigger.
111057      */
111058     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
111059
111060     /**
111061      * @cfg {Date/String} minValue
111062      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
111063      * valid format -- see {@link #format} and {@link #altFormats}.
111064      */
111065
111066     /**
111067      * @cfg {Date/String} maxValue
111068      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
111069      * valid format -- see {@link #format} and {@link #altFormats}.
111070      */
111071
111072     /**
111073      * @cfg {String} minText
111074      * The error text to display when the entered time is before {@link #minValue}.
111075      */
111076     minText : "The time in this field must be equal to or after {0}",
111077
111078     /**
111079      * @cfg {String} maxText
111080      * The error text to display when the entered time is after {@link #maxValue}.
111081      */
111082     maxText : "The time in this field must be equal to or before {0}",
111083
111084     /**
111085      * @cfg {String} invalidText
111086      * The error text to display when the time in the field is invalid.
111087      */
111088     invalidText : "{0} is not a valid time",
111089
111090     /**
111091      * @cfg {String} format
111092      * The default time format string which can be overriden for localization support. The format must be valid
111093      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
111094      * instead.
111095      */
111096     format : "g:i A",
111097
111098     /**
111099      * @cfg {String} submitFormat
111100      * The date format string which will be submitted to the server. The format must be valid according to {@link
111101      * Ext.Date#parse} (defaults to {@link #format}).
111102      */
111103
111104     /**
111105      * @cfg {String} altFormats
111106      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
111107      * format.
111108      */
111109     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",
111110
111111     /**
111112      * @cfg {Number} increment
111113      * The number of minutes between each time value in the list.
111114      */
111115     increment: 15,
111116
111117     /**
111118      * @cfg {Number} pickerMaxHeight
111119      * The maximum height of the {@link Ext.picker.Time} dropdown.
111120      */
111121     pickerMaxHeight: 300,
111122
111123     /**
111124      * @cfg {Boolean} selectOnTab
111125      * Whether the Tab key should select the currently highlighted item.
111126      */
111127     selectOnTab: true,
111128
111129     /**
111130      * @private
111131      * This is the date to use when generating time values in the absence of either minValue
111132      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
111133      * arbitrary "safe" date that can be any date aside from DST boundary dates.
111134      */
111135     initDate: '1/1/2008',
111136     initDateFormat: 'j/n/Y',
111137
111138
111139     initComponent: function() {
111140         var me = this,
111141             min = me.minValue,
111142             max = me.maxValue;
111143         if (min) {
111144             me.setMinValue(min);
111145         }
111146         if (max) {
111147             me.setMaxValue(max);
111148         }
111149         this.callParent();
111150     },
111151
111152     initValue: function() {
111153         var me = this,
111154             value = me.value;
111155
111156         // If a String value was supplied, try to convert it to a proper Date object
111157         if (Ext.isString(value)) {
111158             me.value = me.rawToValue(value);
111159         }
111160
111161         me.callParent();
111162     },
111163
111164     /**
111165      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
111166      * @param {Date/String} value The minimum time that can be selected
111167      */
111168     setMinValue: function(value) {
111169         var me = this,
111170             picker = me.picker;
111171         me.setLimit(value, true);
111172         if (picker) {
111173             picker.setMinValue(me.minValue);
111174         }
111175     },
111176
111177     /**
111178      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
111179      * @param {Date/String} value The maximum time that can be selected
111180      */
111181     setMaxValue: function(value) {
111182         var me = this,
111183             picker = me.picker;
111184         me.setLimit(value, false);
111185         if (picker) {
111186             picker.setMaxValue(me.maxValue);
111187         }
111188     },
111189
111190     /**
111191      * @private
111192      * Updates either the min or max value. Converts the user's value into a Date object whose
111193      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
111194      */
111195     setLimit: function(value, isMin) {
111196         var me = this,
111197             d, val;
111198         if (Ext.isString(value)) {
111199             d = me.parseDate(value);
111200         }
111201         else if (Ext.isDate(value)) {
111202             d = value;
111203         }
111204         if (d) {
111205             val = Ext.Date.clearTime(new Date(me.initDate));
111206             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
111207             me[isMin ? 'minValue' : 'maxValue'] = val;
111208         }
111209     },
111210
111211     rawToValue: function(rawValue) {
111212         return this.parseDate(rawValue) || rawValue || null;
111213     },
111214
111215     valueToRaw: function(value) {
111216         return this.formatDate(this.parseDate(value));
111217     },
111218
111219     /**
111220      * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
111221      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
111222      * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
111223      * set.
111224      * @param {Object} [value] The value to get errors for (defaults to the current field value)
111225      * @return {String[]} All validation errors for this field
111226      */
111227     getErrors: function(value) {
111228         var me = this,
111229             format = Ext.String.format,
111230             errors = me.callParent(arguments),
111231             minValue = me.minValue,
111232             maxValue = me.maxValue,
111233             date;
111234
111235         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
111236
111237         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
111238              return errors;
111239         }
111240
111241         date = me.parseDate(value);
111242         if (!date) {
111243             errors.push(format(me.invalidText, value, me.format));
111244             return errors;
111245         }
111246
111247         if (minValue && date < minValue) {
111248             errors.push(format(me.minText, me.formatDate(minValue)));
111249         }
111250
111251         if (maxValue && date > maxValue) {
111252             errors.push(format(me.maxText, me.formatDate(maxValue)));
111253         }
111254
111255         return errors;
111256     },
111257
111258     formatDate: function() {
111259         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
111260     },
111261
111262     /**
111263      * @private
111264      * Parses an input value into a valid Date object.
111265      * @param {String/Date} value
111266      */
111267     parseDate: function(value) {
111268         if (!value || Ext.isDate(value)) {
111269             return value;
111270         }
111271
111272         var me = this,
111273             val = me.safeParse(value, me.format),
111274             altFormats = me.altFormats,
111275             altFormatsArray = me.altFormatsArray,
111276             i = 0,
111277             len;
111278
111279         if (!val && altFormats) {
111280             altFormatsArray = altFormatsArray || altFormats.split('|');
111281             len = altFormatsArray.length;
111282             for (; i < len && !val; ++i) {
111283                 val = me.safeParse(value, altFormatsArray[i]);
111284             }
111285         }
111286         return val;
111287     },
111288
111289     safeParse: function(value, format){
111290         var me = this,
111291             utilDate = Ext.Date,
111292             parsedDate,
111293             result = null;
111294
111295         if (utilDate.formatContainsDateInfo(format)) {
111296             // assume we've been given a full date
111297             result = utilDate.parse(value, format);
111298         } else {
111299             // Use our initial safe date
111300             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
111301             if (parsedDate) {
111302                 result = parsedDate;
111303             }
111304         }
111305         return result;
111306     },
111307
111308     // @private
111309     getSubmitValue: function() {
111310         var me = this,
111311             format = me.submitFormat || me.format,
111312             value = me.getValue();
111313
111314         return value ? Ext.Date.format(value, format) : null;
111315     },
111316
111317     /**
111318      * @private
111319      * Creates the {@link Ext.picker.Time}
111320      */
111321     createPicker: function() {
111322         var me = this,
111323             picker = Ext.create('Ext.picker.Time', {
111324                 pickerField: me,
111325                 selModel: {
111326                     mode: 'SINGLE'
111327                 },
111328                 floating: true,
111329                 hidden: true,
111330                 minValue: me.minValue,
111331                 maxValue: me.maxValue,
111332                 increment: me.increment,
111333                 format: me.format,
111334                 ownerCt: this.ownerCt,
111335                 renderTo: document.body,
111336                 maxHeight: me.pickerMaxHeight,
111337                 focusOnToFront: false
111338             });
111339
111340         me.mon(picker.getSelectionModel(), {
111341             selectionchange: me.onListSelect,
111342             scope: me
111343         });
111344
111345         return picker;
111346     },
111347
111348     /**
111349      * @private
111350      * Enables the key nav for the Time picker when it is expanded.
111351      * TODO this is largely the same logic as ComboBox, should factor out.
111352      */
111353     onExpand: function() {
111354         var me = this,
111355             keyNav = me.pickerKeyNav,
111356             selectOnTab = me.selectOnTab,
111357             picker = me.getPicker(),
111358             lastSelected = picker.getSelectionModel().lastSelected,
111359             itemNode;
111360
111361         if (!keyNav) {
111362             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
111363                 boundList: picker,
111364                 forceKeyDown: true,
111365                 tab: function(e) {
111366                     if (selectOnTab) {
111367                         if(me.picker.highlightedItem) {
111368                             this.selectHighlighted(e);
111369                         } else {
111370                             me.collapse();
111371                         }
111372                         me.triggerBlur();
111373                     }
111374                     // Tab key event is allowed to propagate to field
111375                     return true;
111376                 }
111377             });
111378             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
111379             if (selectOnTab) {
111380                 me.ignoreMonitorTab = true;
111381             }
111382         }
111383         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
111384
111385         // Highlight the last selected item and scroll it into view
111386         if (lastSelected) {
111387             itemNode = picker.getNode(lastSelected);
111388             if (itemNode) {
111389                 picker.highlightItem(itemNode);
111390                 picker.el.scrollChildIntoView(itemNode, false);
111391             }
111392         }
111393     },
111394
111395     /**
111396      * @private
111397      * Disables the key nav for the Time picker when it is collapsed.
111398      */
111399     onCollapse: function() {
111400         var me = this,
111401             keyNav = me.pickerKeyNav;
111402         if (keyNav) {
111403             keyNav.disable();
111404             me.ignoreMonitorTab = false;
111405         }
111406     },
111407
111408     /**
111409      * @private
111410      * Clears the highlighted item in the picker on change.
111411      * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed.
111412      */
111413     onChange: function() {
111414         var me = this,
111415             picker = me.picker;
111416
111417         me.callParent(arguments);
111418         if(picker) {
111419             picker.clearHighlight();
111420         }
111421     },
111422
111423     /**
111424      * @private
111425      * Handles a time being selected from the Time picker.
111426      */
111427     onListSelect: function(list, recordArray) {
111428         var me = this,
111429             record = recordArray[0],
111430             val = record ? record.get('date') : null;
111431         me.setValue(val);
111432         me.fireEvent('select', me, val);
111433         me.picker.clearHighlight();
111434         me.collapse();
111435         me.inputEl.focus();
111436     }
111437 });
111438
111439
111440 /**
111441  * @class Ext.grid.CellEditor
111442  * @extends Ext.Editor
111443  * Internal utility class that provides default configuration for cell editing.
111444  * @ignore
111445  */
111446 Ext.define('Ext.grid.CellEditor', {
111447     extend: 'Ext.Editor',
111448     constructor: function(config) {
111449         config = Ext.apply({}, config);
111450         
111451         if (config.field) {
111452             config.field.monitorTab = false;
111453         }
111454         if (!Ext.isDefined(config.autoSize)) {
111455             config.autoSize = {
111456                 width: 'boundEl'
111457             };
111458         }
111459         this.callParent([config]);
111460     },
111461     
111462     /**
111463      * @private
111464      * Hide the grid cell when editor is shown.
111465      */
111466     onShow: function() {
111467         var first = this.boundEl.first();
111468         if (first) {
111469             first.hide();
111470         }
111471         this.callParent(arguments);
111472     },
111473     
111474     /**
111475      * @private
111476      * Show grid cell when editor is hidden.
111477      */
111478     onHide: function() {
111479         var first = this.boundEl.first();
111480         if (first) {
111481             first.show();
111482         }
111483         this.callParent(arguments);
111484     },
111485     
111486     /**
111487      * @private
111488      * Fix checkbox blur when it is clicked.
111489      */
111490     afterRender: function() {
111491         this.callParent(arguments);
111492         var field = this.field;
111493         if (field.isXType('checkboxfield')) {
111494             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
111495             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
111496         }
111497     },
111498     
111499     /**
111500      * @private
111501      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
111502      */
111503     onCheckBoxMouseDown: function() {
111504         this.completeEdit = Ext.emptyFn;
111505     },
111506     
111507     /**
111508      * @private
111509      * Restore checkbox focus and completeEdit method.
111510      */
111511     onCheckBoxClick: function() {
111512         delete this.completeEdit;
111513         this.field.focus(false, 10);
111514     },
111515     
111516     alignment: "tl-tl",
111517     hideEl : false,
111518     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
111519     shim: false,
111520     shadow: false
111521 });
111522 /**
111523  * @class Ext.grid.ColumnLayout
111524  * @extends Ext.layout.container.HBox
111525  * @private
111526  *
111527  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
111528  *
111529  * <p>It adds the ability to shrink the vertical size of the inner container element back if a grouped
111530  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
111531  *
111532  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
111533  * <code>setPadding</code> on the columns so that they lay out correctly.</p>
111534  */
111535 Ext.define('Ext.grid.ColumnLayout', {
111536     extend: 'Ext.layout.container.HBox',
111537     alias: 'layout.gridcolumn',
111538     type : 'column',
111539
111540     reserveOffset: false,
111541
111542     shrinkToFit: false,
111543
111544     // Height-stretched innerCt must be able to revert back to unstretched height
111545     clearInnerCtOnLayout: true,
111546
111547     beforeLayout: function() {
111548         var me = this,
111549             i = 0,
111550             items = me.getLayoutItems(),
111551             len = items.length,
111552             item, returnValue,
111553             s;
111554
111555         // Scrollbar offset defined by width of any vertical scroller in the owning grid
111556         if (!Ext.isDefined(me.availableSpaceOffset)) {
111557             s = me.owner.up('tablepanel').verticalScroller;
111558             me.availableSpaceOffset = s ? s.width-1 : 0;
111559         }
111560
111561         returnValue = me.callParent(arguments);
111562
111563         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
111564         me.innerCt.setHeight(23);
111565
111566         // Unstretch child items before the layout which stretches them.
111567         for (; i < len; i++) {
111568             item = items[i];
111569             item.el.setStyle({
111570                 height: 'auto'
111571             });
111572             item.titleContainer.setStyle({
111573                 height: 'auto',
111574                 paddingTop: '0'
111575             });
111576             if (item.componentLayout && item.componentLayout.lastComponentSize) {
111577                 item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
111578             }
111579         }
111580         return returnValue;
111581     },
111582
111583     // Override to enforce the forceFit config.
111584     calculateChildBoxes: function(visibleItems, targetSize) {
111585         var me = this,
111586             calculations = me.callParent(arguments),
111587             boxes = calculations.boxes,
111588             metaData = calculations.meta,
111589             len = boxes.length, i = 0, box, item;
111590
111591         if (targetSize.width && !me.isHeader) {
111592             // If configured forceFit then all columns will be flexed
111593             if (me.owner.forceFit) {
111594
111595                 for (; i < len; i++) {
111596                     box = boxes[i];
111597                     item = box.component;
111598
111599                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
111600                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
111601
111602                     // For forceFit, just use allocated width as the flex value, and the proportions
111603                     // will end up the same whatever HeaderContainer width they are being forced into.
111604                     item.flex = box.width;
111605                 }
111606
111607                 // Recalculate based upon all columns now being flexed instead of sized.
111608                 calculations = me.callParent(arguments);
111609             }
111610             else if (metaData.tooNarrow) {
111611                 targetSize.width = metaData.desiredSize;
111612             }
111613         }
111614
111615         return calculations;
111616     },
111617
111618     afterLayout: function() {
111619         var me = this,
111620             owner = me.owner,
111621             topGrid,
111622             bothHeaderCts,
111623             otherHeaderCt,
111624             thisHeight,
111625             otherHeight,
111626             modifiedGrid,
111627             i = 0,
111628             items,
111629             len,
111630             headerHeight;
111631
111632         me.callParent(arguments);
111633
111634         // Set up padding in items
111635         if (!me.owner.hideHeaders) {
111636
111637             // If this is one HeaderContainer of a pair in a side-by-side locking view, then find the height
111638             // of the highest one, and sync the other one to that height.
111639             if (owner.lockableInjected) {
111640                 topGrid = owner.up('tablepanel').up('tablepanel');
111641                 bothHeaderCts = topGrid.query('headercontainer:not([isHeader])');
111642                 otherHeaderCt = (bothHeaderCts[0] === owner) ? bothHeaderCts[1] : bothHeaderCts[0];
111643
111644                 // Both sides must be rendered for this syncing operation to work.
111645                 if (!otherHeaderCt.rendered) {
111646                     return;
111647                 }
111648
111649                 // Get the height of the highest of both HeaderContainers
111650                 otherHeight = otherHeaderCt.layout.getRenderTarget().getViewSize().height;
111651                 if (!otherHeight) {
111652                     return;
111653                 }
111654                 thisHeight = this.getRenderTarget().getViewSize().height;
111655                 if (!thisHeight) {
111656                     return;
111657                 }
111658
111659                 // Prevent recursion back into here when the "other" grid, after adjusting to the new hight of its headerCt, attempts to inform its ownerCt
111660                 // Block the upward notification by flagging the top grid's component layout as busy.
111661                 topGrid.componentLayout.layoutBusy = true;
111662
111663                 // Assume that the correct header height is the height of this HeaderContainer
111664                 headerHeight = thisHeight;
111665
111666                 // Synch the height of the smaller HeaderContainer to the height of the highest one.
111667                 if (thisHeight > otherHeight) {
111668                     otherHeaderCt.layout.align = 'stretch';
111669                     otherHeaderCt.setCalculatedSize(otherHeaderCt.getWidth(), owner.getHeight(), otherHeaderCt.ownerCt);
111670                     delete otherHeaderCt.layout.align;
111671                     modifiedGrid = otherHeaderCt.up('tablepanel');
111672                 } else if (otherHeight > thisHeight) {
111673                     headerHeight = otherHeight;
111674                     this.align = 'stretch';
111675                     owner.setCalculatedSize(owner.getWidth(), otherHeaderCt.getHeight(), owner.ownerCt);
111676                     delete this.align;
111677                     modifiedGrid = owner.up('tablepanel');
111678                 }
111679                 topGrid.componentLayout.layoutBusy = false;
111680
111681                 // Gather all Header items across both Grids.
111682                 items = bothHeaderCts[0].layout.getLayoutItems().concat(bothHeaderCts[1].layout.getLayoutItems());
111683             } else {
111684                 headerHeight = this.getRenderTarget().getViewSize().height;
111685                 items = me.getLayoutItems();
111686             }
111687
111688             len = items.length;
111689             for (; i < len; i++) {
111690                 items[i].setPadding(headerHeight);
111691             }
111692
111693             // Size the View within the grid which has had its HeaderContainer entallened (That's a perfectly cromulent word BTW)
111694             if (modifiedGrid) {
111695                 setTimeout(function() {
111696                     modifiedGrid.doLayout();
111697                 }, 1);
111698             }
111699         }
111700     },
111701
111702     // FIX: when flexing we actually don't have enough space as we would
111703     // typically because of the scrollOffset on the GridView, must reserve this
111704     updateInnerCtSize: function(tSize, calcs) {
111705         var me = this,
111706             extra;
111707
111708         // Columns must not account for scroll offset
111709         if (!me.isHeader) {
111710             me.tooNarrow = calcs.meta.tooNarrow;
111711             extra = (me.reserveOffset ? me.availableSpaceOffset : 0);
111712
111713             if (calcs.meta.tooNarrow) {
111714                 tSize.width = calcs.meta.desiredSize + extra;
111715             } else {
111716                 tSize.width += extra;
111717             }
111718         }
111719
111720         return me.callParent(arguments);
111721     },
111722
111723     doOwnerCtLayouts: function() {
111724         var ownerCt = this.owner.ownerCt;
111725         if (!ownerCt.componentLayout.layoutBusy) {
111726             ownerCt.doComponentLayout();
111727         }
111728     }
111729 });
111730 /**
111731  * @class Ext.grid.LockingView
111732  * This class is used internally to provide a single interface when using
111733  * a locking grid. Internally, the locking grid creates two separate grids,
111734  * so this class is used to map calls appropriately.
111735  * @ignore
111736  */
111737 Ext.define('Ext.grid.LockingView', {
111738
111739     mixins: {
111740         observable: 'Ext.util.Observable'
111741     },
111742
111743     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
111744
111745     constructor: function(config){
111746         var me = this,
111747             eventNames = [],
111748             eventRe = me.eventRelayRe,
111749             locked = config.locked.getView(),
111750             normal = config.normal.getView(),
111751             events,
111752             event;
111753
111754         Ext.apply(me, {
111755             lockedView: locked,
111756             normalView: normal,
111757             lockedGrid: config.locked,
111758             normalGrid: config.normal,
111759             panel: config.panel
111760         });
111761         me.mixins.observable.constructor.call(me, config);
111762
111763         // relay events
111764         events = locked.events;
111765         for (event in events) {
111766             if (events.hasOwnProperty(event) && eventRe.test(event)) {
111767                 eventNames.push(event);
111768             }
111769         }
111770         me.relayEvents(locked, eventNames);
111771         me.relayEvents(normal, eventNames);
111772
111773         normal.on({
111774             scope: me,
111775             itemmouseleave: me.onItemMouseLeave,
111776             itemmouseenter: me.onItemMouseEnter
111777         });
111778
111779         locked.on({
111780             scope: me,
111781             itemmouseleave: me.onItemMouseLeave,
111782             itemmouseenter: me.onItemMouseEnter
111783         });
111784     },
111785
111786     getGridColumns: function() {
111787         var cols = this.lockedGrid.headerCt.getGridColumns();
111788         return cols.concat(this.normalGrid.headerCt.getGridColumns());
111789     },
111790
111791     getEl: function(column){
111792         return this.getViewForColumn(column).getEl();
111793     },
111794
111795     getViewForColumn: function(column) {
111796         var view = this.lockedView,
111797             inLocked;
111798
111799         view.headerCt.cascade(function(col){
111800             if (col === column) {
111801                 inLocked = true;
111802                 return false;
111803             }
111804         });
111805
111806         return inLocked ? view : this.normalView;
111807     },
111808
111809     onItemMouseEnter: function(view, record){
111810         var me = this,
111811             locked = me.lockedView,
111812             other = me.normalView,
111813             item;
111814
111815         if (view.trackOver) {
111816             if (view !== locked) {
111817                 other = locked;
111818             }
111819             item = other.getNode(record);
111820             other.highlightItem(item);
111821         }
111822     },
111823
111824     onItemMouseLeave: function(view, record){
111825         var me = this,
111826             locked = me.lockedView,
111827             other = me.normalView;
111828
111829         if (view.trackOver) {
111830             if (view !== locked) {
111831                 other = locked;
111832             }
111833             other.clearHighlight();
111834         }
111835     },
111836
111837     relayFn: function(name, args){
111838         args = args || [];
111839
111840         var view = this.lockedView;
111841         view[name].apply(view, args || []);
111842         view = this.normalView;
111843         view[name].apply(view, args || []);
111844     },
111845
111846     getSelectionModel: function(){
111847         return this.panel.getSelectionModel();
111848     },
111849
111850     getStore: function(){
111851         return this.panel.store;
111852     },
111853
111854     getNode: function(nodeInfo){
111855         // default to the normal view
111856         return this.normalView.getNode(nodeInfo);
111857     },
111858
111859     getCell: function(record, column){
111860         var view = this.getViewForColumn(column),
111861             row;
111862
111863         row = view.getNode(record);
111864         return Ext.fly(row).down(column.getCellSelector());
111865     },
111866
111867     getRecord: function(node){
111868         var result = this.lockedView.getRecord(node);
111869         if (!node) {
111870             result = this.normalView.getRecord(node);
111871         }
111872         return result;
111873     },
111874
111875     addElListener: function(eventName, fn, scope){
111876         this.relayFn('addElListener', arguments);
111877     },
111878
111879     refreshNode: function(){
111880         this.relayFn('refreshNode', arguments);
111881     },
111882
111883     refresh: function(){
111884         this.relayFn('refresh', arguments);
111885     },
111886
111887     bindStore: function(){
111888         this.relayFn('bindStore', arguments);
111889     },
111890
111891     addRowCls: function(){
111892         this.relayFn('addRowCls', arguments);
111893     },
111894
111895     removeRowCls: function(){
111896         this.relayFn('removeRowCls', arguments);
111897     }
111898
111899 });
111900 /**
111901  * @class Ext.grid.Lockable
111902  * @private
111903  *
111904  * Lockable is a private mixin which injects lockable behavior into any
111905  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
111906  * automatically inject the Ext.grid.Lockable mixin in when one of the
111907  * these conditions are met:
111908  *
111909  *  - The TablePanel has the lockable configuration set to true
111910  *  - One of the columns in the TablePanel has locked set to true/false
111911  *
111912  * Each TablePanel subclass must register an alias. It should have an array
111913  * of configurations to copy to the 2 separate tablepanel's that will be generated
111914  * to note what configurations should be copied. These are named normalCfgCopy and
111915  * lockedCfgCopy respectively.
111916  *
111917  * Columns which are locked must specify a fixed width. They do NOT support a
111918  * flex width.
111919  *
111920  * Configurations which are specified in this class will be available on any grid or
111921  * tree which is using the lockable functionality.
111922  */
111923 Ext.define('Ext.grid.Lockable', {
111924
111925     requires: ['Ext.grid.LockingView'],
111926
111927     /**
111928      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
111929      * locked grid view. This is turned on by default. If your grid is guaranteed
111930      * to have rows of all the same height, you should set this to false to
111931      * optimize performance.
111932      */
111933     syncRowHeight: true,
111934
111935     /**
111936      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
111937      * not specified lockable will determine the subgrid xtype to create by the
111938      * following rule. Use the superclasses xtype if the superclass is NOT
111939      * tablepanel, otherwise use the xtype itself.
111940      */
111941
111942     /**
111943      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
111944      * locked side of the grid. Any conflicting configurations between lockedViewConfig
111945      * and viewConfig will be overwritten by the lockedViewConfig.
111946      */
111947
111948     /**
111949      * @cfg {Object} normalViewConfig A view configuration to be applied to the
111950      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
111951      * and viewConfig will be overwritten by the normalViewConfig.
111952      */
111953
111954     // private variable to track whether or not the spacer is hidden/visible
111955     spacerHidden: true,
111956
111957     headerCounter: 0,
111958
111959     // i8n text
111960     unlockText: 'Unlock',
111961     lockText: 'Lock',
111962
111963     determineXTypeToCreate: function() {
111964         var me = this,
111965             typeToCreate;
111966
111967         if (me.subGridXType) {
111968             typeToCreate = me.subGridXType;
111969         } else {
111970             var xtypes     = this.getXTypes().split('/'),
111971                 xtypesLn   = xtypes.length,
111972                 xtype      = xtypes[xtypesLn - 1],
111973                 superxtype = xtypes[xtypesLn - 2];
111974
111975             if (superxtype !== 'tablepanel') {
111976                 typeToCreate = superxtype;
111977             } else {
111978                 typeToCreate = xtype;
111979             }
111980         }
111981
111982         return typeToCreate;
111983     },
111984
111985     // injectLockable will be invoked before initComponent's parent class implementation
111986     // is called, so throughout this method this. are configurations
111987     injectLockable: function() {
111988         // ensure lockable is set to true in the TablePanel
111989         this.lockable = true;
111990         // Instruct the TablePanel it already has a view and not to create one.
111991         // We are going to aggregate 2 copies of whatever TablePanel we are using
111992         this.hasView = true;
111993
111994         var me = this,
111995             // xtype of this class, 'treepanel' or 'gridpanel'
111996             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
111997             // alias.)
111998             xtype = me.determineXTypeToCreate(),
111999             // share the selection model
112000             selModel = me.getSelectionModel(),
112001             lockedGrid = {
112002                 xtype: xtype,
112003                 // Lockable does NOT support animations for Tree
112004                 enableAnimations: false,
112005                 scroll: false,
112006                 scrollerOwner: false,
112007                 selModel: selModel,
112008                 border: false,
112009                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
112010             },
112011             normalGrid = {
112012                 xtype: xtype,
112013                 enableAnimations: false,
112014                 scrollerOwner: false,
112015                 selModel: selModel,
112016                 border: false
112017             },
112018             i = 0,
112019             columns,
112020             lockedHeaderCt,
112021             normalHeaderCt;
112022
112023         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
112024
112025         // copy appropriate configurations to the respective
112026         // aggregated tablepanel instances and then delete them
112027         // from the master tablepanel.
112028         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
112029         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
112030         for (; i < me.normalCfgCopy.length; i++) {
112031             delete me[me.normalCfgCopy[i]];
112032         }
112033         for (i = 0; i < me.lockedCfgCopy.length; i++) {
112034             delete me[me.lockedCfgCopy[i]];
112035         }
112036
112037         me.addEvents(
112038             /**
112039              * @event lockcolumn
112040              * Fires when a column is locked.
112041              * @param {Ext.grid.Panel} this The gridpanel.
112042              * @param {Ext.grid.column.Column} column The column being locked.
112043              */
112044             'lockcolumn',
112045
112046             /**
112047              * @event unlockcolumn
112048              * Fires when a column is unlocked.
112049              * @param {Ext.grid.Panel} this The gridpanel.
112050              * @param {Ext.grid.column.Column} column The column being unlocked.
112051              */
112052             'unlockcolumn'
112053         );
112054
112055         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
112056
112057         me.lockedHeights = [];
112058         me.normalHeights = [];
112059
112060         columns = me.processColumns(me.columns);
112061
112062         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
112063         lockedGrid.columns = columns.locked;
112064         normalGrid.columns = columns.normal;
112065
112066         me.store = Ext.StoreManager.lookup(me.store);
112067         lockedGrid.store = me.store;
112068         normalGrid.store = me.store;
112069
112070         // normal grid should flex the rest of the width
112071         normalGrid.flex = 1;
112072         lockedGrid.viewConfig = me.lockedViewConfig || {};
112073         lockedGrid.viewConfig.loadingUseMsg = false;
112074         normalGrid.viewConfig = me.normalViewConfig || {};
112075
112076         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
112077         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
112078
112079         me.normalGrid = Ext.ComponentManager.create(normalGrid);
112080         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
112081
112082         me.view = Ext.create('Ext.grid.LockingView', {
112083             locked: me.lockedGrid,
112084             normal: me.normalGrid,
112085             panel: me
112086         });
112087
112088         if (me.syncRowHeight) {
112089             me.lockedGrid.getView().on({
112090                 refresh: me.onLockedGridAfterRefresh,
112091                 itemupdate: me.onLockedGridAfterUpdate,
112092                 scope: me
112093             });
112094
112095             me.normalGrid.getView().on({
112096                 refresh: me.onNormalGridAfterRefresh,
112097                 itemupdate: me.onNormalGridAfterUpdate,
112098                 scope: me
112099             });
112100         }
112101
112102         lockedHeaderCt = me.lockedGrid.headerCt;
112103         normalHeaderCt = me.normalGrid.headerCt;
112104
112105         lockedHeaderCt.lockedCt = true;
112106         lockedHeaderCt.lockableInjected = true;
112107         normalHeaderCt.lockableInjected = true;
112108
112109         lockedHeaderCt.on({
112110             columnshow: me.onLockedHeaderShow,
112111             columnhide: me.onLockedHeaderHide,
112112             columnmove: me.onLockedHeaderMove,
112113             sortchange: me.onLockedHeaderSortChange,
112114             columnresize: me.onLockedHeaderResize,
112115             scope: me
112116         });
112117
112118         normalHeaderCt.on({
112119             columnmove: me.onNormalHeaderMove,
112120             sortchange: me.onNormalHeaderSortChange,
112121             scope: me
112122         });
112123
112124         me.normalGrid.on({
112125             scrollershow: me.onScrollerShow,
112126             scrollerhide: me.onScrollerHide,
112127             scope: me
112128         });
112129
112130         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
112131
112132         me.modifyHeaderCt();
112133         me.items = [me.lockedGrid, me.normalGrid];
112134
112135         me.relayHeaderCtEvents(lockedHeaderCt);
112136         me.relayHeaderCtEvents(normalHeaderCt);
112137
112138         me.layout = {
112139             type: 'hbox',
112140             align: 'stretch'
112141         };
112142     },
112143
112144     processColumns: function(columns){
112145         // split apart normal and lockedWidths
112146         var i = 0,
112147             len = columns.length,
112148             lockedWidth = 1,
112149             lockedHeaders = [],
112150             normalHeaders = [],
112151             column;
112152
112153         for (; i < len; ++i) {
112154             column = columns[i];
112155             // mark the column as processed so that the locked attribute does not
112156             // trigger trying to aggregate the columns again.
112157             column.processed = true;
112158             if (column.locked) {
112159                 if (column.flex) {
112160                     Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
112161                 }
112162                 if (!column.hidden) {
112163                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
112164                 }
112165                 lockedHeaders.push(column);
112166             } else {
112167                 normalHeaders.push(column);
112168             }
112169             if (!column.headerId) {
112170                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
112171             }
112172         }
112173         return {
112174             lockedWidth: lockedWidth,
112175             locked: lockedHeaders,
112176             normal: normalHeaders
112177         };
112178     },
112179
112180     // create a new spacer after the table is refreshed
112181     onLockedGridAfterLayout: function() {
112182         var me         = this,
112183             lockedView = me.lockedGrid.getView();
112184         lockedView.on({
112185             beforerefresh: me.destroySpacer,
112186             scope: me
112187         });
112188     },
112189
112190     // trigger a pseudo refresh on the normal side
112191     onLockedHeaderMove: function() {
112192         if (this.syncRowHeight) {
112193             this.onNormalGridAfterRefresh();
112194         }
112195     },
112196
112197     // trigger a pseudo refresh on the locked side
112198     onNormalHeaderMove: function() {
112199         if (this.syncRowHeight) {
112200             this.onLockedGridAfterRefresh();
112201         }
112202     },
112203
112204     // create a spacer in lockedsection and store a reference
112205     // TODO: Should destroy before refreshing content
112206     getSpacerEl: function() {
112207         var me   = this,
112208             w,
112209             view,
112210             el;
112211
112212         if (!me.spacerEl) {
112213             // This affects scrolling all the way to the bottom of a locked grid
112214             // additional test, sort a column and make sure it synchronizes
112215             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
112216             view = me.lockedGrid.getView();
112217             el   = view.el;
112218
112219             me.spacerEl = Ext.DomHelper.append(el, {
112220                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
112221                 style: 'height: ' + w + 'px;'
112222             }, true);
112223         }
112224         return me.spacerEl;
112225     },
112226
112227     destroySpacer: function() {
112228         var me = this;
112229         if (me.spacerEl) {
112230             me.spacerEl.destroy();
112231             delete me.spacerEl;
112232         }
112233     },
112234
112235     // cache the heights of all locked rows and sync rowheights
112236     onLockedGridAfterRefresh: function() {
112237         var me     = this,
112238             view   = me.lockedGrid.getView(),
112239             el     = view.el,
112240             rowEls = el.query(view.getItemSelector()),
112241             ln     = rowEls.length,
112242             i = 0;
112243
112244         // reset heights each time.
112245         me.lockedHeights = [];
112246
112247         for (; i < ln; i++) {
112248             me.lockedHeights[i] = rowEls[i].clientHeight;
112249         }
112250         me.syncRowHeights();
112251     },
112252
112253     // cache the heights of all normal rows and sync rowheights
112254     onNormalGridAfterRefresh: function() {
112255         var me     = this,
112256             view   = me.normalGrid.getView(),
112257             el     = view.el,
112258             rowEls = el.query(view.getItemSelector()),
112259             ln     = rowEls.length,
112260             i = 0;
112261
112262         // reset heights each time.
112263         me.normalHeights = [];
112264
112265         for (; i < ln; i++) {
112266             me.normalHeights[i] = rowEls[i].clientHeight;
112267         }
112268         me.syncRowHeights();
112269     },
112270
112271     // rows can get bigger/smaller
112272     onLockedGridAfterUpdate: function(record, index, node) {
112273         this.lockedHeights[index] = node.clientHeight;
112274         this.syncRowHeights();
112275     },
112276
112277     // rows can get bigger/smaller
112278     onNormalGridAfterUpdate: function(record, index, node) {
112279         this.normalHeights[index] = node.clientHeight;
112280         this.syncRowHeights();
112281     },
112282
112283     // match the rowheights to the biggest rowheight on either
112284     // side
112285     syncRowHeights: function() {
112286         var me = this,
112287             lockedHeights = me.lockedHeights,
112288             normalHeights = me.normalHeights,
112289             calcHeights   = [],
112290             ln = lockedHeights.length,
112291             i  = 0,
112292             lockedView, normalView,
112293             lockedRowEls, normalRowEls,
112294             vertScroller = me.getVerticalScroller(),
112295             scrollTop;
112296
112297         // ensure there are an equal num of locked and normal
112298         // rows before synchronization
112299         if (lockedHeights.length && normalHeights.length) {
112300             lockedView = me.lockedGrid.getView();
112301             normalView = me.normalGrid.getView();
112302             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
112303             normalRowEls = normalView.el.query(normalView.getItemSelector());
112304
112305             // loop thru all of the heights and sync to the other side
112306             for (; i < ln; i++) {
112307                 // ensure both are numbers
112308                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
112309                     if (lockedHeights[i] > normalHeights[i]) {
112310                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
112311                     } else if (lockedHeights[i] < normalHeights[i]) {
112312                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
112313                     }
112314                 }
112315             }
112316
112317             // invalidate the scroller and sync the scrollers
112318             me.normalGrid.invalidateScroller();
112319
112320             // synchronize the view with the scroller, if we have a virtualScrollTop
112321             // then the user is using a PagingScroller
112322             if (vertScroller && vertScroller.setViewScrollTop) {
112323                 vertScroller.setViewScrollTop(me.virtualScrollTop);
112324             } else {
112325                 // We don't use setScrollTop here because if the scrollTop is
112326                 // set to the exact same value some browsers won't fire the scroll
112327                 // event. Instead, we directly set the scrollTop.
112328                 scrollTop = normalView.el.dom.scrollTop;
112329                 normalView.el.dom.scrollTop = scrollTop;
112330                 lockedView.el.dom.scrollTop = scrollTop;
112331             }
112332
112333             // reset the heights
112334             me.lockedHeights = [];
112335             me.normalHeights = [];
112336         }
112337     },
112338
112339     // track when scroller is shown
112340     onScrollerShow: function(scroller, direction) {
112341         if (direction === 'horizontal') {
112342             this.spacerHidden = false;
112343             this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
112344         }
112345     },
112346
112347     // track when scroller is hidden
112348     onScrollerHide: function(scroller, direction) {
112349         if (direction === 'horizontal') {
112350             this.spacerHidden = true;
112351             if (this.spacerEl) {
112352                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
112353             }
112354         }
112355     },
112356
112357
112358     // inject Lock and Unlock text
112359     modifyHeaderCt: function() {
112360         var me = this;
112361         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
112362         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
112363     },
112364
112365     onUnlockMenuClick: function() {
112366         this.unlock();
112367     },
112368
112369     onLockMenuClick: function() {
112370         this.lock();
112371     },
112372
112373     getMenuItems: function(locked) {
112374         var me            = this,
112375             unlockText    = me.unlockText,
112376             lockText      = me.lockText,
112377             unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
112378             lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
112379             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
112380             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
112381
112382         // runs in the scope of headerCt
112383         return function() {
112384             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
112385             o.push('-',{
112386                 cls: unlockCls,
112387                 text: unlockText,
112388                 handler: unlockHandler,
112389                 disabled: !locked
112390             });
112391             o.push({
112392                 cls: lockCls,
112393                 text: lockText,
112394                 handler: lockHandler,
112395                 disabled: locked
112396             });
112397             return o;
112398         };
112399     },
112400
112401     // going from unlocked section to locked
112402     /**
112403      * Locks the activeHeader as determined by which menu is open OR a header
112404      * as specified.
112405      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
112406      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
112407      * @private
112408      */
112409     lock: function(activeHd, toIdx) {
112410         var me         = this,
112411             normalGrid = me.normalGrid,
112412             lockedGrid = me.lockedGrid,
112413             normalHCt  = normalGrid.headerCt,
112414             lockedHCt  = lockedGrid.headerCt;
112415
112416         activeHd = activeHd || normalHCt.getMenu().activeHeader;
112417
112418         // if column was previously flexed, get/set current width
112419         // and remove the flex
112420         if (activeHd.flex) {
112421             activeHd.width = activeHd.getWidth();
112422             delete activeHd.flex;
112423         }
112424
112425         normalHCt.remove(activeHd, false);
112426         lockedHCt.suspendLayout = true;
112427         activeHd.locked = true;
112428         if (Ext.isDefined(toIdx)) {
112429             lockedHCt.insert(toIdx, activeHd);
112430         } else {
112431             lockedHCt.add(activeHd);
112432         }
112433         lockedHCt.suspendLayout = false;
112434         me.syncLockedSection();
112435
112436         me.fireEvent('lockcolumn', me, activeHd);
112437     },
112438
112439     syncLockedSection: function() {
112440         var me = this;
112441         me.syncLockedWidth();
112442         me.lockedGrid.getView().refresh();
112443         me.normalGrid.getView().refresh();
112444     },
112445
112446     // adjust the locked section to the width of its respective
112447     // headerCt
112448     syncLockedWidth: function() {
112449         var me = this,
112450             width = me.lockedGrid.headerCt.getFullWidth(true);
112451         me.lockedGrid.setWidth(width+1); // +1 for border pixel
112452         me.doComponentLayout();
112453     },
112454
112455     onLockedHeaderResize: function() {
112456         this.syncLockedWidth();
112457     },
112458
112459     onLockedHeaderHide: function() {
112460         this.syncLockedWidth();
112461     },
112462
112463     onLockedHeaderShow: function() {
112464         this.syncLockedWidth();
112465     },
112466
112467     onLockedHeaderSortChange: function(headerCt, header, sortState) {
112468         if (sortState) {
112469             // no real header, and silence the event so we dont get into an
112470             // infinite loop
112471             this.normalGrid.headerCt.clearOtherSortStates(null, true);
112472         }
112473     },
112474
112475     onNormalHeaderSortChange: function(headerCt, header, sortState) {
112476         if (sortState) {
112477             // no real header, and silence the event so we dont get into an
112478             // infinite loop
112479             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
112480         }
112481     },
112482
112483     // going from locked section to unlocked
112484     /**
112485      * Unlocks the activeHeader as determined by which menu is open OR a header
112486      * as specified.
112487      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
112488      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
112489      * @private
112490      */
112491     unlock: function(activeHd, toIdx) {
112492         var me         = this,
112493             normalGrid = me.normalGrid,
112494             lockedGrid = me.lockedGrid,
112495             normalHCt  = normalGrid.headerCt,
112496             lockedHCt  = lockedGrid.headerCt;
112497
112498         if (!Ext.isDefined(toIdx)) {
112499             toIdx = 0;
112500         }
112501         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
112502
112503         lockedHCt.remove(activeHd, false);
112504         me.syncLockedWidth();
112505         me.lockedGrid.getView().refresh();
112506         activeHd.locked = false;
112507         normalHCt.insert(toIdx, activeHd);
112508         me.normalGrid.getView().refresh();
112509
112510         me.fireEvent('unlockcolumn', me, activeHd);
112511     },
112512
112513     applyColumnsState: function (columns) {
112514         var me = this,
112515             lockedGrid = me.lockedGrid,
112516             lockedHeaderCt = lockedGrid.headerCt,
112517             normalHeaderCt = me.normalGrid.headerCt,
112518             lockedCols = lockedHeaderCt.items,
112519             normalCols = normalHeaderCt.items,
112520             existing,
112521             locked = [],
112522             normal = [],
112523             lockedDefault,
112524             lockedWidth = 1;
112525
112526         Ext.each(columns, function (col) {
112527             function matches (item) {
112528                 return item.headerId == col.id;
112529             }
112530
112531             lockedDefault = true;
112532             if (!(existing = lockedCols.findBy(matches))) {
112533                 existing = normalCols.findBy(matches);
112534                 lockedDefault = false;
112535             }
112536
112537             if (existing) {
112538                 if (existing.applyColumnState) {
112539                     existing.applyColumnState(col);
112540                 }
112541                 if (!Ext.isDefined(existing.locked)) {
112542                     existing.locked = lockedDefault;
112543                 }
112544                 if (existing.locked) {
112545                     locked.push(existing);
112546                     if (!existing.hidden && Ext.isNumber(existing.width)) {
112547                         lockedWidth += existing.width;
112548                     }
112549                 } else {
112550                     normal.push(existing);
112551                 }
112552             }
112553         });
112554
112555         // state and config must have the same columns (compare counts for now):
112556         if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
112557             lockedHeaderCt.removeAll(false);
112558             normalHeaderCt.removeAll(false);
112559
112560             lockedHeaderCt.add(locked);
112561             normalHeaderCt.add(normal);
112562
112563             lockedGrid.setWidth(lockedWidth);
112564         }
112565     },
112566
112567     getColumnsState: function () {
112568         var me = this,
112569             locked = me.lockedGrid.headerCt.getColumnsState(),
112570             normal = me.normalGrid.headerCt.getColumnsState();
112571
112572         return locked.concat(normal);
112573     },
112574
112575     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
112576     reconfigureLockable: function(store, columns) {
112577         var me = this,
112578             lockedGrid = me.lockedGrid,
112579             normalGrid = me.normalGrid;
112580
112581         if (columns) {
112582             lockedGrid.headerCt.suspendLayout = true;
112583             normalGrid.headerCt.suspendLayout = true;
112584             lockedGrid.headerCt.removeAll();
112585             normalGrid.headerCt.removeAll();
112586
112587             columns = me.processColumns(columns);
112588             lockedGrid.setWidth(columns.lockedWidth);
112589             lockedGrid.headerCt.add(columns.locked);
112590             normalGrid.headerCt.add(columns.normal);
112591         }
112592
112593         if (store) {
112594             store = Ext.data.StoreManager.lookup(store);
112595             me.store = store;
112596             lockedGrid.bindStore(store);
112597             normalGrid.bindStore(store);
112598         } else {
112599             lockedGrid.getView().refresh();
112600             normalGrid.getView().refresh();
112601         }
112602
112603         if (columns) {
112604             lockedGrid.headerCt.suspendLayout = false;
112605             normalGrid.headerCt.suspendLayout = false;
112606             lockedGrid.headerCt.forceComponentLayout();
112607             normalGrid.headerCt.forceComponentLayout();
112608         }
112609     }
112610 });
112611
112612 /**
112613  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
112614  * across different sections.
112615  */
112616 Ext.define('Ext.grid.Scroller', {
112617     extend: 'Ext.Component',
112618     alias: 'widget.gridscroller',
112619     weight: 110,
112620     baseCls: Ext.baseCSSPrefix + 'scroller',
112621     focusable: false,
112622     reservedSpace: 0,
112623
112624     renderTpl: [
112625         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
112626             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
112627         '</div>'
112628     ],
112629
112630     initComponent: function() {
112631         var me       = this,
112632             dock     = me.dock,
112633             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
112634
112635         me.offsets = {bottom: 0};
112636         me.scrollProp = 'scrollTop';
112637         me.vertical = true;
112638         me.sizeProp = 'width';
112639
112640         if (dock === 'top' || dock === 'bottom') {
112641             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
112642             me.sizeProp = 'height';
112643             me.scrollProp = 'scrollLeft';
112644             me.vertical = false;
112645             me.weight += 5;
112646         }
112647
112648         me.cls += (' ' + cls);
112649
112650         Ext.applyIf(me.renderSelectors, {
112651             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
112652             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
112653         });
112654         me.callParent();
112655     },
112656     
112657     ensureDimension: function(){
112658         var me = this,
112659             sizeProp = me.sizeProp;
112660             
112661         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
112662     },
112663
112664     initRenderData: function () {
112665         var me = this,
112666             ret = me.callParent(arguments) || {};
112667
112668         ret.baseId = me.id;
112669
112670         return ret;
112671     },
112672
112673     afterRender: function() {
112674         var me = this;
112675         me.callParent();
112676         
112677         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
112678         Ext.cache[me.el.id].skipGarbageCollection = true;
112679     },
112680
112681     onAdded: function(container) {
112682         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
112683         this.ownerGrid = container;
112684         this.callParent(arguments);
112685     },
112686
112687     getSizeCalculation: function() {
112688         var me     = this,
112689             owner  = me.getPanel(),
112690             width  = 1,
112691             height = 1,
112692             view, tbl;
112693
112694         if (!me.vertical) {
112695             // TODO: Must gravitate to a single region..
112696             // Horizontal scrolling only scrolls virtualized region
112697             var items  = owner.query('tableview'),
112698                 center = items[1] || items[0];
112699
112700             if (!center) {
112701                 return false;
112702             }
112703             // center is not guaranteed to have content, such as when there
112704             // are zero rows in the grid/tree. We read the width from the
112705             // headerCt instead.
112706             width = center.headerCt.getFullWidth();
112707
112708             if (Ext.isIEQuirks) {
112709                 width--;
112710             }
112711         } else {
112712             view = owner.down('tableview:not([lockableInjected])');
112713             if (!view || !view.el) {
112714                 return false;
112715             }
112716             tbl = view.el.child('table', true);
112717             if (!tbl) {
112718                 return false;
112719             }
112720
112721             // needs to also account for header and scroller (if still in picture)
112722             // should calculate from headerCt.
112723             height = tbl.offsetHeight;
112724         }
112725         if (isNaN(width)) {
112726             width = 1;
112727         }
112728         if (isNaN(height)) {
112729             height = 1;
112730         }
112731         return {
112732             width: width,
112733             height: height
112734         };
112735     },
112736
112737     invalidate: function(firstPass) {
112738         var me = this,
112739             stretchEl = me.stretchEl;
112740
112741         if (!stretchEl || !me.ownerCt) {
112742             return;
112743         }
112744
112745         var size  = me.getSizeCalculation(),
112746             scrollEl = me.scrollEl,
112747             elDom = scrollEl.dom,
112748             reservedSpace = me.reservedSpace,
112749             pos,
112750             extra = 5;
112751
112752         if (size) {
112753             stretchEl.setSize(size);
112754
112755             size = me.el.getSize(true);
112756
112757             if (me.vertical) {
112758                 size.width += extra;
112759                 size.height -= reservedSpace;
112760                 pos = 'left';
112761             } else {
112762                 size.width -= reservedSpace;
112763                 size.height += extra;
112764                 pos = 'top';
112765             }
112766
112767             scrollEl.setSize(size);
112768             elDom.style[pos] = (-extra) + 'px';
112769
112770             // BrowserBug: IE7
112771             // This makes the scroller enabled, when initially rendering.
112772             elDom.scrollTop = elDom.scrollTop;
112773         }
112774     },
112775
112776     afterComponentLayout: function() {
112777         this.callParent(arguments);
112778         this.invalidate();
112779     },
112780
112781     restoreScrollPos: function () {
112782         var me = this,
112783             el = this.scrollEl,
112784             elDom = el && el.dom;
112785
112786         if (me._scrollPos !== null && elDom) {
112787             elDom[me.scrollProp] = me._scrollPos;
112788             me._scrollPos = null;
112789         }
112790     },
112791
112792     setReservedSpace: function (reservedSpace) {
112793         var me = this;
112794         if (me.reservedSpace !== reservedSpace) {
112795             me.reservedSpace = reservedSpace;
112796             me.invalidate();
112797         }
112798     },
112799
112800     saveScrollPos: function () {
112801         var me = this,
112802             el = this.scrollEl,
112803             elDom = el && el.dom;
112804
112805         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
112806     },
112807
112808     /**
112809      * Sets the scrollTop and constrains the value between 0 and max.
112810      * @param {Number} scrollTop
112811      * @return {Number} The resulting scrollTop value after being constrained
112812      */
112813     setScrollTop: function(scrollTop) {
112814         var el = this.scrollEl,
112815             elDom = el && el.dom;
112816
112817         if (elDom) {
112818             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
112819         }
112820     },
112821
112822     /**
112823      * Sets the scrollLeft and constrains the value between 0 and max.
112824      * @param {Number} scrollLeft
112825      * @return {Number} The resulting scrollLeft value after being constrained
112826      */
112827     setScrollLeft: function(scrollLeft) {
112828         var el = this.scrollEl,
112829             elDom = el && el.dom;
112830
112831         if (elDom) {
112832             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
112833         }
112834     },
112835
112836     /**
112837      * Scroll by deltaY
112838      * @param {Number} delta
112839      * @return {Number} The resulting scrollTop value
112840      */
112841     scrollByDeltaY: function(delta) {
112842         var el = this.scrollEl,
112843             elDom = el && el.dom;
112844
112845         if (elDom) {
112846             return this.setScrollTop(elDom.scrollTop + delta);
112847         }
112848     },
112849
112850     /**
112851      * Scroll by deltaX
112852      * @param {Number} delta
112853      * @return {Number} The resulting scrollLeft value
112854      */
112855     scrollByDeltaX: function(delta) {
112856         var el = this.scrollEl,
112857             elDom = el && el.dom;
112858
112859         if (elDom) {
112860             return this.setScrollLeft(elDom.scrollLeft + delta);
112861         }
112862     },
112863
112864
112865     /**
112866      * Scroll to the top.
112867      */
112868     scrollToTop : function(){
112869         this.setScrollTop(0);
112870     },
112871
112872     // synchronize the scroller with the bound gridviews
112873     onElScroll: function(event, target) {
112874         this.fireEvent('bodyscroll', event, target);
112875     },
112876
112877     getPanel: function() {
112878         var me = this;
112879         if (!me.panel) {
112880             me.panel = this.up('[scrollerOwner]');
112881         }
112882         return me.panel;
112883     }
112884 });
112885
112886
112887 /**
112888  * @class Ext.grid.PagingScroller
112889  * @extends Ext.grid.Scroller
112890  */
112891 Ext.define('Ext.grid.PagingScroller', {
112892     extend: 'Ext.grid.Scroller',
112893     alias: 'widget.paginggridscroller',
112894     //renderTpl: null,
112895     //tpl: [
112896     //    '<tpl for="pages">',
112897     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
112898     //    '</tpl>'
112899     //],
112900     /**
112901      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
112902      * at what percentage to begin fetching the next page. For example if the pageSize is 100
112903      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
112904      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
112905      */
112906     percentageFromEdge: 0.35,
112907
112908     /**
112909      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
112910      * when scrolling the PagingScrollbar.
112911      */
112912     scrollToLoadBuffer: 200,
112913
112914     activePrefetch: true,
112915
112916     chunkSize: 50,
112917     snapIncrement: 25,
112918
112919     syncScroll: true,
112920
112921     initComponent: function() {
112922         var me = this,
112923             ds = me.store;
112924
112925         ds.on('guaranteedrange', me.onGuaranteedRange, me);
112926         me.callParent(arguments);
112927     },
112928
112929     onGuaranteedRange: function(range, start, end) {
112930         var me = this,
112931             ds = me.store,
112932             rs;
112933         // this should never happen
112934         if (range.length && me.visibleStart < range[0].index) {
112935             return;
112936         }
112937
112938         ds.loadRecords(range);
112939
112940         if (!me.firstLoad) {
112941             if (me.rendered) {
112942                 me.invalidate();
112943             } else {
112944                 me.on('afterrender', me.invalidate, me, {single: true});
112945             }
112946             me.firstLoad = true;
112947         } else {
112948             // adjust to visible
112949             // only sync if there is a paging scrollbar element and it has a scroll height (meaning it's currently in the DOM)
112950             if (me.scrollEl && me.scrollEl.dom && me.scrollEl.dom.scrollHeight) {
112951                 me.syncTo();
112952             }
112953         }
112954     },
112955
112956     syncTo: function() {
112957         var me            = this,
112958             pnl           = me.getPanel(),
112959             store         = pnl.store,
112960             scrollerElDom = this.scrollEl.dom,
112961             rowOffset     = me.visibleStart - store.guaranteedStart,
112962             scrollBy      = rowOffset * me.rowHeight,
112963             scrollHeight  = scrollerElDom.scrollHeight,
112964             clientHeight  = scrollerElDom.clientHeight,
112965             scrollTop     = scrollerElDom.scrollTop,
112966             useMaximum;
112967             
112968
112969         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
112970         // Instead we are using offsetHeight and hardcoding borders
112971         if (Ext.isIE9 && Ext.isStrict) {
112972             clientHeight = scrollerElDom.offsetHeight + 2;
112973         }
112974
112975         // This should always be zero or greater than zero but staying
112976         // safe and less than 0 we'll scroll to the bottom.
112977         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
112978         this.setViewScrollTop(scrollBy, useMaximum);
112979     },
112980
112981     getPageData : function(){
112982         var panel = this.getPanel(),
112983             store = panel.store,
112984             totalCount = store.getTotalCount();
112985
112986         return {
112987             total : totalCount,
112988             currentPage : store.currentPage,
112989             pageCount: Math.ceil(totalCount / store.pageSize),
112990             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
112991             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
112992         };
112993     },
112994
112995     onElScroll: function(e, t) {
112996         var me = this,
112997             panel = me.getPanel(),
112998             store = panel.store,
112999             pageSize = store.pageSize,
113000             guaranteedStart = store.guaranteedStart,
113001             guaranteedEnd = store.guaranteedEnd,
113002             totalCount = store.getTotalCount(),
113003             numFromEdge = Math.ceil(me.percentageFromEdge * pageSize),
113004             position = t.scrollTop,
113005             visibleStart = Math.floor(position / me.rowHeight),
113006             view = panel.down('tableview'),
113007             viewEl = view.el,
113008             visibleHeight = viewEl.getHeight(),
113009             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
113010             visibleEnd = visibleStart + visibleAhead,
113011             prevPage = Math.floor(visibleStart / pageSize),
113012             nextPage = Math.floor(visibleEnd / pageSize) + 2,
113013             lastPage = Math.ceil(totalCount / pageSize),
113014             snap = me.snapIncrement,
113015             requestStart = Math.floor(visibleStart / snap) * snap,
113016             requestEnd = requestStart + pageSize - 1,
113017             activePrefetch = me.activePrefetch;
113018
113019         me.visibleStart = visibleStart;
113020         me.visibleEnd = visibleEnd;
113021         
113022         
113023         me.syncScroll = true;
113024         if (totalCount >= pageSize) {
113025             // end of request was past what the total is, grab from the end back a pageSize
113026             if (requestEnd > totalCount - 1) {
113027                 me.cancelLoad();
113028                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
113029                     me.syncScroll = true;
113030                 }
113031                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
113032             // Out of range, need to reset the current data set
113033             } else if (visibleStart <= guaranteedStart || visibleEnd > guaranteedEnd) {
113034                 if (visibleStart <= guaranteedStart) {
113035                     // need to scroll up
113036                     requestStart -= snap;
113037                     requestEnd -= snap;
113038                     
113039                     if (requestStart < 0) {
113040                         requestStart = 0;
113041                         requestEnd = pageSize;
113042                     }
113043                 }
113044                 if (store.rangeSatisfied(requestStart, requestEnd)) {
113045                     me.cancelLoad();
113046                     store.guaranteeRange(requestStart, requestEnd);
113047                 } else {
113048                     store.mask();
113049                     me.attemptLoad(requestStart, requestEnd);
113050                 }
113051                 // dont sync the scroll view immediately, sync after the range has been guaranteed
113052                 me.syncScroll = false;
113053             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
113054                 me.syncScroll = true;
113055                 store.prefetchPage(prevPage);
113056             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
113057                 me.syncScroll = true;
113058                 store.prefetchPage(nextPage);
113059             }
113060         }
113061
113062         if (me.syncScroll) {
113063             me.syncTo();
113064         }
113065     },
113066
113067     getSizeCalculation: function() {
113068         // Use the direct ownerCt here rather than the scrollerOwner
113069         // because we are calculating widths/heights.
113070         var me     = this,
113071             owner  = me.ownerGrid,
113072             view   = owner.getView(),
113073             store  = me.store,
113074             dock   = me.dock,
113075             elDom  = me.el.dom,
113076             width  = 1,
113077             height = 1;
113078
113079         if (!me.rowHeight) {
113080             me.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
113081         }
113082
113083         // If the Store is *locally* filtered, use the filtered count from getCount.
113084         height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * me.rowHeight;
113085
113086         if (isNaN(width)) {
113087             width = 1;
113088         }
113089         if (isNaN(height)) {
113090             height = 1;
113091         }
113092         return {
113093             width: width,
113094             height: height
113095         };
113096     },
113097
113098     attemptLoad: function(start, end) {
113099         var me = this;
113100         if (!me.loadTask) {
113101             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
113102         }
113103         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
113104     },
113105
113106     cancelLoad: function() {
113107         if (this.loadTask) {
113108             this.loadTask.cancel();
113109         }
113110     },
113111
113112     doAttemptLoad:  function(start, end) {
113113         var store = this.getPanel().store;
113114         store.guaranteeRange(start, end);
113115     },
113116
113117     setViewScrollTop: function(scrollTop, useMax) {
113118         var me = this,
113119             owner = me.getPanel(),
113120             items = owner.query('tableview'),
113121             i = 0,
113122             len = items.length,
113123             center,
113124             centerEl,
113125             calcScrollTop,
113126             maxScrollTop,
113127             scrollerElDom = me.el.dom;
113128
113129         owner.virtualScrollTop = scrollTop;
113130
113131         center = items[1] || items[0];
113132         centerEl = center.el.dom;
113133
113134         maxScrollTop = ((owner.store.pageSize * me.rowHeight) - centerEl.clientHeight);
113135         calcScrollTop = (scrollTop % ((owner.store.pageSize * me.rowHeight) + 1));
113136         if (useMax) {
113137             calcScrollTop = maxScrollTop;
113138         }
113139         if (calcScrollTop > maxScrollTop) {
113140             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
113141             return;
113142             // calcScrollTop = maxScrollTop;
113143         }
113144         for (; i < len; i++) {
113145             items[i].el.dom.scrollTop = calcScrollTop;
113146         }
113147     }
113148 });
113149
113150 /**
113151  * @author Nicolas Ferrero
113152  *
113153  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
113154  *
113155  * TablePanel aggregates:
113156  *
113157  *  - a Selection Model
113158  *  - a View
113159  *  - a Store
113160  *  - Scrollers
113161  *  - Ext.grid.header.Container
113162  */
113163 Ext.define('Ext.panel.Table', {
113164     extend: 'Ext.panel.Panel',
113165
113166     alias: 'widget.tablepanel',
113167
113168     uses: [
113169         'Ext.selection.RowModel',
113170         'Ext.grid.Scroller',
113171         'Ext.grid.header.Container',
113172         'Ext.grid.Lockable'
113173     ],
113174
113175     extraBaseCls: Ext.baseCSSPrefix + 'grid',
113176     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
113177
113178     layout: 'fit',
113179     /**
113180      * @property {Boolean} hasView
113181      * True to indicate that a view has been injected into the panel.
113182      */
113183     hasView: false,
113184
113185     // each panel should dictate what viewType and selType to use
113186     /**
113187      * @cfg {String} viewType
113188      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
113189      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
113190      */
113191     viewType: null,
113192
113193     /**
113194      * @cfg {Object} viewConfig
113195      * A config object that will be applied to the grid's UI view. Any of the config options available for
113196      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
113197      */
113198
113199     /**
113200      * @cfg {Ext.view.Table} view
113201      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
113202      * view (instead of creating an entire View instance).
113203      */
113204
113205     /**
113206      * @cfg {String} selType
113207      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
113208      * a config object or nothing at all given in {@link #selModel} config.
113209      */
113210     selType: 'rowmodel',
113211
113212     /**
113213      * @cfg {Ext.selection.Model/Object} selModel
113214      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
113215      * config option determines to which type of selection model this config is applied.
113216      */
113217
113218     /**
113219      * @cfg {Boolean} multiSelect
113220      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
113221      */
113222
113223     /**
113224      * @cfg {Boolean} simpleSelect
113225      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
113226      */
113227
113228     /**
113229      * @cfg {Ext.data.Store} store (required)
113230      * The {@link Ext.data.Store Store} the grid should use as its data source.
113231      */
113232
113233     /**
113234      * @cfg {Number} scrollDelta
113235      * Number of pixels to scroll when scrolling with mousewheel.
113236      */
113237     scrollDelta: 40,
113238
113239     /**
113240      * @cfg {String/Boolean} scroll
113241      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
113242      * True implies 'both'. False implies 'none'.
113243      */
113244     scroll: true,
113245
113246     /**
113247      * @cfg {Ext.grid.column.Column[]} columns
113248      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
113249      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
113250      * column comes from.
113251      */
113252
113253     /**
113254      * @cfg {Boolean} forceFit
113255      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
113256      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
113257      * content width is used.
113258      */
113259
113260     /**
113261      * @cfg {Ext.grid.feature.Feature[]} features
113262      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
113263      */
113264
113265     /**
113266      * @cfg {Boolean} [hideHeaders=false]
113267      * True to hide column headers.
113268      */
113269
113270     /**
113271      * @cfg {Boolean} deferRowRender
113272      * Defaults to true to enable deferred row rendering.
113273      *
113274      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
113275      * that layouts with GridPanels appear, and lay out more quickly.
113276      */
113277
113278      deferRowRender: true,
113279      
113280     /**
113281      * @cfg {Boolean} sortableColumns
113282      * False to disable column sorting via clicking the header and via the Sorting menu items.
113283      */
113284     sortableColumns: true,
113285
113286     /**
113287      * @cfg {Boolean} [enableLocking=false]
113288      * True to enable locking support for this grid. Alternatively, locking will also be automatically
113289      * enabled if any of the columns in the column configuration contain the locked config option.
113290      */
113291     enableLocking: false,
113292
113293     verticalScrollDock: 'right',
113294     verticalScrollerType: 'gridscroller',
113295
113296     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
113297     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
113298
113299     // private property used to determine where to go down to find views
113300     // this is here to support locking.
113301     scrollerOwner: true,
113302
113303     invalidateScrollerOnRefresh: true,
113304
113305     /**
113306      * @cfg {Boolean} enableColumnMove
113307      * False to disable column dragging within this grid.
113308      */
113309     enableColumnMove: true,
113310
113311     /**
113312      * @cfg {Boolean} enableColumnResize
113313      * False to disable column resizing within this grid.
113314      */
113315     enableColumnResize: true,
113316
113317     /**
113318      * @cfg {Boolean} enableColumnHide
113319      * False to disable column hiding within this grid.
113320      */
113321     enableColumnHide: true,
113322
113323     initComponent: function() {
113324         if (!this.viewType) {
113325             Ext.Error.raise("You must specify a viewType config.");
113326         }
113327         if (this.headers) {
113328             Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
113329         }
113330
113331         var me          = this,
113332             scroll      = me.scroll,
113333             vertical    = false,
113334             horizontal  = false,
113335             headerCtCfg = me.columns || me.colModel,
113336             i           = 0,
113337             view,
113338             border = me.border;
113339
113340         if (me.hideHeaders) {
113341             border = false;
113342         }
113343
113344         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
113345         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
113346
113347         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
113348         // Either way, we extract a columns property referencing an array of Column definitions.
113349         if (headerCtCfg instanceof Ext.grid.header.Container) {
113350             me.headerCt = headerCtCfg;
113351             me.headerCt.border = border;
113352             me.columns = me.headerCt.items.items;
113353         } else {
113354             if (Ext.isArray(headerCtCfg)) {
113355                 headerCtCfg = {
113356                     items: headerCtCfg,
113357                     border: border
113358                 };
113359             }
113360             Ext.apply(headerCtCfg, {
113361                 forceFit: me.forceFit,
113362                 sortable: me.sortableColumns,
113363                 enableColumnMove: me.enableColumnMove,
113364                 enableColumnResize: me.enableColumnResize,
113365                 enableColumnHide: me.enableColumnHide,
113366                 border:  border
113367             });
113368             me.columns = headerCtCfg.items;
113369
113370              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
113371              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
113372              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
113373                  me.self.mixin('lockable', Ext.grid.Lockable);
113374                  me.injectLockable();
113375              }
113376         }
113377
113378         me.addEvents(
113379             /**
113380              * @event reconfigure
113381              * Fires after a reconfigure.
113382              * @param {Ext.panel.Table} this
113383              */
113384             'reconfigure',
113385             /**
113386              * @event viewready
113387              * Fires when the grid view is available (use this for selecting a default row).
113388              * @param {Ext.panel.Table} this
113389              */
113390             'viewready',
113391             /**
113392              * @event scrollerhide
113393              * Fires when a scroller is hidden.
113394              * @param {Ext.grid.Scroller} scroller
113395              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
113396              */
113397             'scrollerhide',
113398             /**
113399              * @event scrollershow
113400              * Fires when a scroller is shown.
113401              * @param {Ext.grid.Scroller} scroller
113402              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
113403              */
113404             'scrollershow'
113405         );
113406
113407         me.bodyCls = me.bodyCls || '';
113408         me.bodyCls += (' ' + me.extraBodyCls);
113409         
113410         me.cls = me.cls || '';
113411         me.cls += (' ' + me.extraBaseCls);
113412
113413         // autoScroll is not a valid configuration
113414         delete me.autoScroll;
113415
113416         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
113417         // 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.
113418         if (!me.hasView) {
113419
113420             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
113421             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
113422             if (!me.headerCt) {
113423                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
113424             }
113425
113426             // Extract the array of Column objects
113427             me.columns = me.headerCt.items.items;
113428
113429             if (me.hideHeaders) {
113430                 me.headerCt.height = 0;
113431                 me.headerCt.border = false;
113432                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
113433                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
113434                 // IE Quirks Mode fix
113435                 // If hidden configuration option was used, several layout calculations will be bypassed.
113436                 if (Ext.isIEQuirks) {
113437                     me.headerCt.style = {
113438                         display: 'none'
113439                     };
113440                 }
113441             }
113442
113443             // turn both on.
113444             if (scroll === true || scroll === 'both') {
113445                 vertical = horizontal = true;
113446             } else if (scroll === 'horizontal') {
113447                 horizontal = true;
113448             } else if (scroll === 'vertical') {
113449                 vertical = true;
113450             // All other values become 'none' or false.
113451             } else {
113452                 me.headerCt.availableSpaceOffset = 0;
113453             }
113454
113455             if (vertical) {
113456                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
113457                 me.mon(me.verticalScroller, {
113458                     bodyscroll: me.onVerticalScroll,
113459                     scope: me
113460                 });
113461             }
113462
113463             if (horizontal) {
113464                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
113465                 me.mon(me.horizontalScroller, {
113466                     bodyscroll: me.onHorizontalScroll,
113467                     scope: me
113468                 });
113469             }
113470
113471             me.headerCt.on('resize', me.onHeaderResize, me);
113472             me.relayHeaderCtEvents(me.headerCt);
113473             me.features = me.features || [];
113474             if (!Ext.isArray(me.features)) {
113475                 me.features = [me.features];
113476             }
113477             me.dockedItems = me.dockedItems || [];
113478             me.dockedItems.unshift(me.headerCt);
113479             me.viewConfig = me.viewConfig || {};
113480             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
113481
113482             // AbstractDataView will look up a Store configured as an object
113483             // getView converts viewConfig into a View instance
113484             view = me.getView();
113485
113486             view.on({
113487                 afterrender: function () {
113488                     // hijack the view el's scroll method
113489                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
113490                     // We use to listen to document.body wheel events, but that's a
113491                     // little much. We scope just to the view now.
113492                     me.mon(view.el, {
113493                         mousewheel: me.onMouseWheel,
113494                         scope: me
113495                     });
113496                 },
113497                 single: true
113498             });
113499             me.items = [view];
113500             me.hasView = true;
113501
113502             me.mon(view.store, {
113503                 load: me.onStoreLoad,
113504                 scope: me
113505             });
113506             me.mon(view, {
113507                 viewReady: me.onViewReady,
113508                 resize: me.onViewResize,
113509                 refresh: {
113510                     fn: me.onViewRefresh,
113511                     scope: me,
113512                     buffer: 50
113513                 },
113514                 scope: me
113515             });
113516             this.relayEvents(view, [
113517                 /**
113518                  * @event beforeitemmousedown
113519                  * @alias Ext.view.View#beforeitemmousedown
113520                  */
113521                 'beforeitemmousedown',
113522                 /**
113523                  * @event beforeitemmouseup
113524                  * @alias Ext.view.View#beforeitemmouseup
113525                  */
113526                 'beforeitemmouseup',
113527                 /**
113528                  * @event beforeitemmouseenter
113529                  * @alias Ext.view.View#beforeitemmouseenter
113530                  */
113531                 'beforeitemmouseenter',
113532                 /**
113533                  * @event beforeitemmouseleave
113534                  * @alias Ext.view.View#beforeitemmouseleave
113535                  */
113536                 'beforeitemmouseleave',
113537                 /**
113538                  * @event beforeitemclick
113539                  * @alias Ext.view.View#beforeitemclick
113540                  */
113541                 'beforeitemclick',
113542                 /**
113543                  * @event beforeitemdblclick
113544                  * @alias Ext.view.View#beforeitemdblclick
113545                  */
113546                 'beforeitemdblclick',
113547                 /**
113548                  * @event beforeitemcontextmenu
113549                  * @alias Ext.view.View#beforeitemcontextmenu
113550                  */
113551                 'beforeitemcontextmenu',
113552                 /**
113553                  * @event itemmousedown
113554                  * @alias Ext.view.View#itemmousedown
113555                  */
113556                 'itemmousedown',
113557                 /**
113558                  * @event itemmouseup
113559                  * @alias Ext.view.View#itemmouseup
113560                  */
113561                 'itemmouseup',
113562                 /**
113563                  * @event itemmouseenter
113564                  * @alias Ext.view.View#itemmouseenter
113565                  */
113566                 'itemmouseenter',
113567                 /**
113568                  * @event itemmouseleave
113569                  * @alias Ext.view.View#itemmouseleave
113570                  */
113571                 'itemmouseleave',
113572                 /**
113573                  * @event itemclick
113574                  * @alias Ext.view.View#itemclick
113575                  */
113576                 'itemclick',
113577                 /**
113578                  * @event itemdblclick
113579                  * @alias Ext.view.View#itemdblclick
113580                  */
113581                 'itemdblclick',
113582                 /**
113583                  * @event itemcontextmenu
113584                  * @alias Ext.view.View#itemcontextmenu
113585                  */
113586                 'itemcontextmenu',
113587                 /**
113588                  * @event beforecontainermousedown
113589                  * @alias Ext.view.View#beforecontainermousedown
113590                  */
113591                 'beforecontainermousedown',
113592                 /**
113593                  * @event beforecontainermouseup
113594                  * @alias Ext.view.View#beforecontainermouseup
113595                  */
113596                 'beforecontainermouseup',
113597                 /**
113598                  * @event beforecontainermouseover
113599                  * @alias Ext.view.View#beforecontainermouseover
113600                  */
113601                 'beforecontainermouseover',
113602                 /**
113603                  * @event beforecontainermouseout
113604                  * @alias Ext.view.View#beforecontainermouseout
113605                  */
113606                 'beforecontainermouseout',
113607                 /**
113608                  * @event beforecontainerclick
113609                  * @alias Ext.view.View#beforecontainerclick
113610                  */
113611                 'beforecontainerclick',
113612                 /**
113613                  * @event beforecontainerdblclick
113614                  * @alias Ext.view.View#beforecontainerdblclick
113615                  */
113616                 'beforecontainerdblclick',
113617                 /**
113618                  * @event beforecontainercontextmenu
113619                  * @alias Ext.view.View#beforecontainercontextmenu
113620                  */
113621                 'beforecontainercontextmenu',
113622                 /**
113623                  * @event containermouseup
113624                  * @alias Ext.view.View#containermouseup
113625                  */
113626                 'containermouseup',
113627                 /**
113628                  * @event containermouseover
113629                  * @alias Ext.view.View#containermouseover
113630                  */
113631                 'containermouseover',
113632                 /**
113633                  * @event containermouseout
113634                  * @alias Ext.view.View#containermouseout
113635                  */
113636                 'containermouseout',
113637                 /**
113638                  * @event containerclick
113639                  * @alias Ext.view.View#containerclick
113640                  */
113641                 'containerclick',
113642                 /**
113643                  * @event containerdblclick
113644                  * @alias Ext.view.View#containerdblclick
113645                  */
113646                 'containerdblclick',
113647                 /**
113648                  * @event containercontextmenu
113649                  * @alias Ext.view.View#containercontextmenu
113650                  */
113651                 'containercontextmenu',
113652                 /**
113653                  * @event selectionchange
113654                  * @alias Ext.selection.Model#selectionchange
113655                  */
113656                 'selectionchange',
113657                 /**
113658                  * @event beforeselect
113659                  * @alias Ext.selection.RowModel#beforeselect
113660                  */
113661                 'beforeselect',
113662                 /**
113663                  * @event select
113664                  * @alias Ext.selection.RowModel#select
113665                  */
113666                 'select',
113667                 /**
113668                  * @event beforedeselect
113669                  * @alias Ext.selection.RowModel#beforedeselect
113670                  */
113671                 'beforedeselect',
113672                 /**
113673                  * @event deselect
113674                  * @alias Ext.selection.RowModel#deselect
113675                  */
113676                 'deselect'
113677             ]);
113678         }
113679
113680         me.callParent(arguments);
113681     },
113682     
113683     onRender: function(){
113684         var vScroll = this.verticalScroller,
113685             hScroll = this.horizontalScroller;
113686
113687         if (vScroll) {
113688             vScroll.ensureDimension();
113689         }
113690         if (hScroll) {
113691             hScroll.ensureDimension();
113692         }
113693         this.callParent(arguments);    
113694     },
113695
113696     // state management
113697     initStateEvents: function(){
113698         var events = this.stateEvents;
113699         // push on stateEvents if they don't exist
113700         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
113701             if (Ext.Array.indexOf(events, event)) {
113702                 events.push(event);
113703             }
113704         });
113705         this.callParent();
113706     },
113707
113708     /**
113709      * Returns the horizontal scroller config.
113710      */
113711     initHorizontalScroller: function () {
113712         var me = this,
113713             ret = {
113714                 xtype: 'gridscroller',
113715                 dock: 'bottom',
113716                 section: me,
113717                 store: me.store
113718             };
113719
113720         return ret;
113721     },
113722
113723     /**
113724      * Returns the vertical scroller config.
113725      */
113726     initVerticalScroller: function () {
113727         var me = this,
113728             ret = me.verticalScroller || {};
113729
113730         Ext.applyIf(ret, {
113731             xtype: me.verticalScrollerType,
113732             dock: me.verticalScrollDock,
113733             store: me.store
113734         });
113735
113736         return ret;
113737     },
113738
113739     relayHeaderCtEvents: function (headerCt) {
113740         this.relayEvents(headerCt, [
113741             /**
113742              * @event columnresize
113743              * @alias Ext.grid.header.Container#columnresize
113744              */
113745             'columnresize',
113746             /**
113747              * @event columnmove
113748              * @alias Ext.grid.header.Container#columnmove
113749              */
113750             'columnmove',
113751             /**
113752              * @event columnhide
113753              * @alias Ext.grid.header.Container#columnhide
113754              */
113755             'columnhide',
113756             /**
113757              * @event columnshow
113758              * @alias Ext.grid.header.Container#columnshow
113759              */
113760             'columnshow',
113761             /**
113762              * @event sortchange
113763              * @alias Ext.grid.header.Container#sortchange
113764              */
113765             'sortchange'
113766         ]);
113767     },
113768
113769     getState: function(){
113770         var me = this,
113771             state = me.callParent(),
113772             sorter = me.store.sorters.first();
113773
113774         state.columns = (me.headerCt || me).getColumnsState();
113775
113776         if (sorter) {
113777             state.sort = {
113778                 property: sorter.property,
113779                 direction: sorter.direction
113780             };
113781         }
113782
113783         return state;
113784     },
113785
113786     applyState: function(state) {
113787         var me = this,
113788             sorter = state.sort,
113789             store = me.store,
113790             columns = state.columns;
113791
113792         delete state.columns;
113793
113794         // Ensure superclass has applied *its* state.
113795         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
113796         me.callParent(arguments);
113797
113798         if (columns) {
113799             (me.headerCt || me).applyColumnsState(columns);
113800         }
113801
113802         if (sorter) {
113803             if (store.remoteSort) {
113804                 store.sorters.add(Ext.create('Ext.util.Sorter', {
113805                     property: sorter.property,
113806                     direction: sorter.direction
113807                 }));
113808             }
113809             else {
113810                 store.sort(sorter.property, sorter.direction);
113811             }
113812         }
113813     },
113814
113815     /**
113816      * Returns the store associated with this Panel.
113817      * @return {Ext.data.Store} The store
113818      */
113819     getStore: function(){
113820         return this.store;
113821     },
113822
113823     /**
113824      * Gets the view for this panel.
113825      * @return {Ext.view.Table}
113826      */
113827     getView: function() {
113828         var me = this,
113829             sm;
113830
113831         if (!me.view) {
113832             sm = me.getSelectionModel();
113833             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
113834                 deferInitialRefresh: me.deferRowRender,
113835                 xtype: me.viewType,
113836                 store: me.store,
113837                 headerCt: me.headerCt,
113838                 selModel: sm,
113839                 features: me.features,
113840                 panel: me
113841             }));
113842             me.mon(me.view, {
113843                 uievent: me.processEvent,
113844                 scope: me
113845             });
113846             sm.view = me.view;
113847             me.headerCt.view = me.view;
113848             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
113849         }
113850         return me.view;
113851     },
113852
113853     /**
113854      * @private
113855      * @override
113856      * autoScroll is never valid for all classes which extend TablePanel.
113857      */
113858     setAutoScroll: Ext.emptyFn,
113859
113860     // This method hijacks Ext.view.Table's el scroll method.
113861     // This enables us to keep the virtualized scrollbars in sync
113862     // with the view. It currently does NOT support animation.
113863     elScroll: function(direction, distance, animate) {
113864         var me = this,
113865             scroller;
113866
113867         if (direction === "up" || direction === "left") {
113868             distance = -distance;
113869         }
113870         
113871         if (direction === "down" || direction === "up") {
113872             scroller = me.getVerticalScroller();
113873             
113874             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
113875             if (scroller) {
113876                 scroller.scrollByDeltaY(distance);
113877             }
113878         } else {
113879             scroller = me.getHorizontalScroller();
113880             
113881             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
113882             if (scroller) {
113883                 scroller.scrollByDeltaX(distance);
113884             }
113885         }
113886     },
113887
113888     /**
113889      * @private
113890      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
113891      * @param {String} type Event type, eg 'click'
113892      * @param {Ext.view.Table} view TableView Component
113893      * @param {HTMLElement} cell Cell HtmlElement the event took place within
113894      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
113895      * @param {Number} cellIndex Cell index within the row
113896      * @param {Ext.EventObject} e Original event
113897      */
113898     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
113899         var me = this,
113900             header;
113901
113902         if (cellIndex !== -1) {
113903             header = me.headerCt.getGridColumns()[cellIndex];
113904             return header.processEvent.apply(header, arguments);
113905         }
113906     },
113907
113908     /**
113909      * Requests a recalculation of scrollbars and puts them in if they are needed.
113910      */
113911     determineScrollbars: function() {
113912         // Set a flag so that afterComponentLayout does not recurse back into here.
113913         if (this.determineScrollbarsRunning) {
113914             return;
113915         }
113916         this.determineScrollbarsRunning = true;
113917         var me = this,
113918             view = me.view,
113919             box,
113920             tableEl,
113921             scrollWidth,
113922             clientWidth,
113923             scrollHeight,
113924             clientHeight,
113925             verticalScroller = me.verticalScroller,
113926             horizontalScroller = me.horizontalScroller,
113927             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
113928                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
113929             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
113930
113931         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
113932         if (!me.collapsed && view && view.viewReady) {
113933
113934             // Calculate maximum, *scrollbarless* space which the view has available.
113935             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
113936             box = view.el.getSize();
113937
113938             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
113939             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
113940
113941             // Calculate the width of the scrolling block
113942             // There will never be a horizontal scrollbar if all columns are flexed.
113943
113944             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
113945
113946             // Calculate the height of the scrolling block
113947             if (verticalScroller && verticalScroller.el) {
113948                 scrollHeight = verticalScroller.getSizeCalculation().height;
113949             } else {
113950                 tableEl = view.el.child('table', true);
113951                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
113952             }
113953
113954             // View is too high.
113955             // Definitely need a vertical scrollbar
113956             if (scrollHeight > clientHeight) {
113957                 reqScrollbars = 1;
113958
113959                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
113960                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
113961                     reqScrollbars = 3;
113962                 }
113963             }
113964
113965             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
113966             else {
113967                 // View is too wide.
113968                 // Definitely need a horizontal scrollbar
113969                 if (scrollWidth > clientWidth) {
113970                     reqScrollbars = 2;
113971
113972                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
113973                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
113974                         reqScrollbars = 3;
113975                     }
113976                 }
113977             }
113978
113979             // If scrollbar requirements have changed, change 'em...
113980             if (reqScrollbars !== curScrollbars) {
113981
113982                 // Suspend component layout while we add/remove the docked scrollers
113983                 me.suspendLayout = true;
113984                 if (reqScrollbars & 1) {
113985                     me.showVerticalScroller();
113986                 } else {
113987                     me.hideVerticalScroller();
113988                 }
113989                 if (reqScrollbars & 2) {
113990                     me.showHorizontalScroller();
113991                 } else {
113992                     me.hideHorizontalScroller();
113993                 }
113994                 me.suspendLayout = false;
113995
113996                 // Lay out the Component.
113997                 me.doComponentLayout();
113998                 // Lay out me.items
113999                 me.getLayout().layout();
114000             }
114001         }
114002         delete me.determineScrollbarsRunning;
114003     },
114004
114005     onViewResize: function() {
114006         this.determineScrollbars();
114007     },
114008
114009     afterComponentLayout: function() {
114010         this.callParent(arguments);
114011         this.determineScrollbars();
114012         this.invalidateScroller();
114013     },
114014
114015     onHeaderResize: function() {
114016         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
114017             this.determineScrollbars();
114018             this.invalidateScroller();
114019         }
114020     },
114021
114022     afterCollapse: function() {
114023         var me = this;
114024         if (me.verticalScroller) {
114025             me.verticalScroller.saveScrollPos();
114026         }
114027         if (me.horizontalScroller) {
114028             me.horizontalScroller.saveScrollPos();
114029         }
114030         me.callParent(arguments);
114031     },
114032
114033     afterExpand: function() {
114034         var me = this;
114035         me.callParent(arguments);
114036         if (me.verticalScroller) {
114037             me.verticalScroller.restoreScrollPos();
114038         }
114039         if (me.horizontalScroller) {
114040             me.horizontalScroller.restoreScrollPos();
114041         }
114042     },
114043
114044     /**
114045      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
114046      */
114047     hideHorizontalScroller: function() {
114048         var me = this;
114049
114050         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
114051             me.verticalScroller.setReservedSpace(0);
114052             me.removeDocked(me.horizontalScroller, false);
114053             me.removeCls(me.horizontalScrollerPresentCls);
114054             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
114055         }
114056
114057     },
114058
114059     /**
114060      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
114061      */
114062     showHorizontalScroller: function() {
114063         var me = this;
114064
114065         if (me.verticalScroller) {
114066             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
114067         }
114068         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
114069             me.addDocked(me.horizontalScroller);
114070             me.addCls(me.horizontalScrollerPresentCls);
114071             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
114072         }
114073     },
114074
114075     /**
114076      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
114077      */
114078     hideVerticalScroller: function() {
114079         var me = this;
114080
114081         me.setHeaderReserveOffset(false);
114082         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
114083             me.removeDocked(me.verticalScroller, false);
114084             me.removeCls(me.verticalScrollerPresentCls);
114085             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
114086         }
114087     },
114088
114089     /**
114090      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
114091      */
114092     showVerticalScroller: function() {
114093         var me = this;
114094
114095         me.setHeaderReserveOffset(true);
114096         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
114097             me.addDocked(me.verticalScroller);
114098             me.addCls(me.verticalScrollerPresentCls);
114099             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
114100         }
114101     },
114102
114103     setHeaderReserveOffset: function (reserveOffset) {
114104         var headerCt = this.headerCt,
114105             layout = headerCt.layout;
114106
114107         // only trigger a layout when reserveOffset is changing
114108         if (layout && layout.reserveOffset !== reserveOffset) {
114109             layout.reserveOffset = reserveOffset;
114110             if (!this.suspendLayout) {
114111                 headerCt.doLayout();
114112             }
114113         }
114114     },
114115
114116     /**
114117      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
114118      */
114119     invalidateScroller: function() {
114120         var me = this,
114121             vScroll = me.verticalScroller,
114122             hScroll = me.horizontalScroller;
114123
114124         if (vScroll) {
114125             vScroll.invalidate();
114126         }
114127         if (hScroll) {
114128             hScroll.invalidate();
114129         }
114130     },
114131
114132     // refresh the view when a header moves
114133     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
114134         this.view.refresh();
114135     },
114136
114137     // Section onHeaderHide is invoked after view.
114138     onHeaderHide: function(headerCt, header) {
114139         this.invalidateScroller();
114140     },
114141
114142     onHeaderShow: function(headerCt, header) {
114143         this.invalidateScroller();
114144     },
114145
114146     getVerticalScroller: function() {
114147         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
114148     },
114149
114150     getHorizontalScroller: function() {
114151         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
114152     },
114153
114154     onMouseWheel: function(e) {
114155         var me = this,
114156             vertScroller = me.getVerticalScroller(),
114157             horizScroller = me.getHorizontalScroller(),
114158             scrollDelta = -me.scrollDelta,
114159             deltas = e.getWheelDeltas(),
114160             deltaX = scrollDelta * deltas.x,
114161             deltaY = scrollDelta * deltas.y,
114162             vertScrollerEl, horizScrollerEl,
114163             vertScrollerElDom, horizScrollerElDom,
114164             horizontalCanScrollLeft, horizontalCanScrollRight,
114165             verticalCanScrollDown, verticalCanScrollUp;
114166
114167         // calculate whether or not both scrollbars can scroll right/left and up/down
114168         if (horizScroller) {
114169             horizScrollerEl = horizScroller.scrollEl;
114170             if (horizScrollerEl) {
114171                 horizScrollerElDom = horizScrollerEl.dom;
114172                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
114173                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
114174             }
114175         }
114176         if (vertScroller) {
114177             vertScrollerEl = vertScroller.scrollEl;
114178             if (vertScrollerEl) {
114179                 vertScrollerElDom = vertScrollerEl.dom;
114180                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
114181                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
114182             }
114183         }
114184
114185         if (horizScroller) {
114186             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
114187                 e.stopEvent();
114188                 horizScroller.scrollByDeltaX(deltaX);
114189             }
114190         }
114191         if (vertScroller) {
114192             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
114193                 e.stopEvent();
114194                 vertScroller.scrollByDeltaY(deltaY);
114195             }
114196         }
114197     },
114198
114199     /**
114200      * @private
114201      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
114202      */
114203     onViewReady: function() {
114204         var me = this;
114205         me.fireEvent('viewready', me);
114206         if (me.deferRowRender) {
114207             me.determineScrollbars();
114208             me.invalidateScroller();
114209         }
114210     },
114211
114212     /**
114213      * @private
114214      * Determines and invalidates scrollers on view refresh
114215      */
114216     onViewRefresh: function() {
114217         var me = this;
114218
114219         // Refresh *during* render must be ignored.
114220         if (!me.rendering) {
114221             this.determineScrollbars();
114222             if (this.invalidateScrollerOnRefresh) {
114223                 this.invalidateScroller();
114224             }
114225         }
114226     },
114227
114228     /**
114229      * Sets the scrollTop of the TablePanel.
114230      * @param {Number} top
114231      */
114232     setScrollTop: function(top) {
114233         var me               = this,
114234             rootCmp          = me.getScrollerOwner(),
114235             verticalScroller = me.getVerticalScroller();
114236
114237         rootCmp.virtualScrollTop = top;
114238         if (verticalScroller) {
114239             verticalScroller.setScrollTop(top);
114240         }
114241     },
114242
114243     getScrollerOwner: function() {
114244         var rootCmp = this;
114245         if (!this.scrollerOwner) {
114246             rootCmp = this.up('[scrollerOwner]');
114247         }
114248         return rootCmp;
114249     },
114250
114251     /**
114252      * Scrolls the TablePanel by deltaY
114253      * @param {Number} deltaY
114254      */
114255     scrollByDeltaY: function(deltaY) {
114256         var verticalScroller = this.getVerticalScroller();
114257
114258         if (verticalScroller) {
114259             verticalScroller.scrollByDeltaY(deltaY);
114260         }
114261     },
114262
114263     /**
114264      * Scrolls the TablePanel by deltaX
114265      * @param {Number} deltaX
114266      */
114267     scrollByDeltaX: function(deltaX) {
114268         var horizontalScroller = this.getHorizontalScroller();
114269
114270         if (horizontalScroller) {
114271             horizontalScroller.scrollByDeltaX(deltaX);
114272         }
114273     },
114274
114275     /**
114276      * Gets left hand side marker for header resizing.
114277      * @private
114278      */
114279     getLhsMarker: function() {
114280         var me = this;
114281
114282         if (!me.lhsMarker) {
114283             me.lhsMarker = Ext.DomHelper.append(me.el, {
114284                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
114285             }, true);
114286         }
114287         return me.lhsMarker;
114288     },
114289
114290     /**
114291      * Gets right hand side marker for header resizing.
114292      * @private
114293      */
114294     getRhsMarker: function() {
114295         var me = this;
114296
114297         if (!me.rhsMarker) {
114298             me.rhsMarker = Ext.DomHelper.append(me.el, {
114299                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
114300             }, true);
114301         }
114302         return me.rhsMarker;
114303     },
114304
114305     /**
114306      * Returns the selection model being used and creates it via the configuration if it has not been created already.
114307      * @return {Ext.selection.Model} selModel
114308      */
114309     getSelectionModel: function(){
114310         if (!this.selModel) {
114311             this.selModel = {};
114312         }
114313
114314         var mode = 'SINGLE',
114315             type;
114316         if (this.simpleSelect) {
114317             mode = 'SIMPLE';
114318         } else if (this.multiSelect) {
114319             mode = 'MULTI';
114320         }
114321
114322         Ext.applyIf(this.selModel, {
114323             allowDeselect: this.allowDeselect,
114324             mode: mode
114325         });
114326
114327         if (!this.selModel.events) {
114328             type = this.selModel.selType || this.selType;
114329             this.selModel = Ext.create('selection.' + type, this.selModel);
114330         }
114331
114332         if (!this.selModel.hasRelaySetup) {
114333             this.relayEvents(this.selModel, [
114334                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
114335             ]);
114336             this.selModel.hasRelaySetup = true;
114337         }
114338
114339         // lock the selection model if user
114340         // has disabled selection
114341         if (this.disableSelection) {
114342             this.selModel.locked = true;
114343         }
114344         return this.selModel;
114345     },
114346
114347     onVerticalScroll: function(event, target) {
114348         var owner = this.getScrollerOwner(),
114349             items = owner.query('tableview'),
114350             i = 0,
114351             len = items.length;
114352
114353         for (; i < len; i++) {
114354             items[i].el.dom.scrollTop = target.scrollTop;
114355         }
114356     },
114357
114358     onHorizontalScroll: function(event, target) {
114359         var owner = this.getScrollerOwner(),
114360             items = owner.query('tableview'),
114361             center = items[1] || items[0];
114362
114363         center.el.dom.scrollLeft = target.scrollLeft;
114364         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
114365     },
114366
114367     // template method meant to be overriden
114368     onStoreLoad: Ext.emptyFn,
114369
114370     getEditorParent: function() {
114371         return this.body;
114372     },
114373
114374     bindStore: function(store) {
114375         var me = this;
114376         me.store = store;
114377         me.getView().bindStore(store);
114378     },
114379     
114380     beforeDestroy: function(){
114381         // may be some duplication here since the horizontal and vertical
114382         // scroller may be part of the docked items, but we need to clean
114383         // them up in case they aren't visible.
114384         Ext.destroy(this.horizontalScroller, this.verticalScroller);
114385         this.callParent();
114386     },
114387
114388     /**
114389      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
114390      * to change them.
114391      * @param {Ext.data.Store} store (Optional) The new store.
114392      * @param {Object[]} columns (Optional) An array of column configs
114393      */
114394     reconfigure: function(store, columns) {
114395         var me = this,
114396             headerCt = me.headerCt;
114397
114398         if (me.lockable) {
114399             me.reconfigureLockable(store, columns);
114400         } else {
114401             if (columns) {
114402                 headerCt.suspendLayout = true;
114403                 headerCt.removeAll();
114404                 headerCt.add(columns);
114405             }
114406             if (store) {
114407                 store = Ext.StoreManager.lookup(store);
114408                 me.bindStore(store);
114409             } else {
114410                 me.getView().refresh();
114411             }
114412             if (columns) {
114413                 headerCt.suspendLayout = false;
114414                 me.forceComponentLayout();
114415             }
114416         }
114417         me.fireEvent('reconfigure', me);
114418     }
114419 });
114420 /**
114421  * This class encapsulates the user interface for a tabular data set.
114422  * It acts as a centralized manager for controlling the various interface
114423  * elements of the view. This includes handling events, such as row and cell
114424  * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
114425  * to provide visual feedback to the user.
114426  *
114427  * This class does not provide ways to manipulate the underlying data of the configured
114428  * {@link Ext.data.Store}.
114429  *
114430  * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
114431  * to be used directly.
114432  */
114433 Ext.define('Ext.view.Table', {
114434     extend: 'Ext.view.View',
114435     alias: 'widget.tableview',
114436     uses: [
114437         'Ext.view.TableChunker',
114438         'Ext.util.DelayedTask',
114439         'Ext.util.MixedCollection'
114440     ],
114441
114442     baseCls: Ext.baseCSSPrefix + 'grid-view',
114443
114444     // row
114445     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
114446     // cell
114447     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
114448
114449     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
114450     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
114451     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
114452     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
114453     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
114454     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
114455     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
114456
114457     // cfg docs inherited
114458     trackOver: true,
114459
114460     /**
114461      * Override this function to apply custom CSS classes to rows during rendering. This function should return the
114462      * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
114463      * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
114464      * Example usage:
114465      *
114466      *     viewConfig: {
114467      *         getRowClass: function(record, rowIndex, rowParams, store){
114468      *             return record.get("valid") ? "row-valid" : "row-error";
114469      *         }
114470      *     }
114471      *
114472      * @param {Ext.data.Model} record The record corresponding to the current row.
114473      * @param {Number} index The row index.
114474      * @param {Object} rowParams **DEPRECATED.** For row body use the
114475      * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
114476      * @param {Ext.data.Store} store The store this grid is bound to
114477      * @return {String} a CSS class name to add to the row.
114478      * @method
114479      */
114480     getRowClass: null,
114481
114482     initComponent: function() {
114483         var me = this;
114484
114485         me.scrollState = {};
114486         me.selModel.view = me;
114487         me.headerCt.view = me;
114488         me.initFeatures();
114489         me.tpl = '<div></div>';
114490         me.callParent();
114491         me.mon(me.store, {
114492             load: me.onStoreLoad,
114493             scope: me
114494         });
114495
114496         // this.addEvents(
114497         //     /**
114498         //      * @event rowfocus
114499         //      * @param {Ext.data.Model} record
114500         //      * @param {HTMLElement} row
114501         //      * @param {Number} rowIdx
114502         //      */
114503         //     'rowfocus'
114504         // );
114505     },
114506
114507     // scroll to top of the grid when store loads
114508     onStoreLoad: function(){
114509         var me = this;
114510
114511         if (me.invalidateScrollerOnRefresh) {
114512             if (Ext.isGecko) {
114513                 if (!me.scrollToTopTask) {
114514                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
114515                 }
114516                 me.scrollToTopTask.delay(1);
114517             } else {
114518                 me    .scrollToTop();
114519             }
114520         }
114521     },
114522
114523     // scroll the view to the top
114524     scrollToTop: Ext.emptyFn,
114525
114526     /**
114527      * Add a listener to the main view element. It will be destroyed with the view.
114528      * @private
114529      */
114530     addElListener: function(eventName, fn, scope){
114531         this.mon(this, eventName, fn, scope, {
114532             element: 'el'
114533         });
114534     },
114535
114536     /**
114537      * Get the columns used for generating a template via TableChunker.
114538      * See {@link Ext.grid.header.Container#getGridColumns}.
114539      * @private
114540      */
114541     getGridColumns: function() {
114542         return this.headerCt.getGridColumns();
114543     },
114544
114545     /**
114546      * Get a leaf level header by index regardless of what the nesting
114547      * structure is.
114548      * @private
114549      * @param {Number} index The index
114550      */
114551     getHeaderAtIndex: function(index) {
114552         return this.headerCt.getHeaderAtIndex(index);
114553     },
114554
114555     /**
114556      * Get the cell (td) for a particular record and column.
114557      * @param {Ext.data.Model} record
114558      * @param {Ext.grid.column.Column} column
114559      * @private
114560      */
114561     getCell: function(record, column) {
114562         var row = this.getNode(record);
114563         return Ext.fly(row).down(column.getCellSelector());
114564     },
114565
114566     /**
114567      * Get a reference to a feature
114568      * @param {String} id The id of the feature
114569      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
114570      */
114571     getFeature: function(id) {
114572         var features = this.featuresMC;
114573         if (features) {
114574             return features.get(id);
114575         }
114576     },
114577
114578     /**
114579      * Initializes each feature and bind it to this view.
114580      * @private
114581      */
114582     initFeatures: function() {
114583         var me = this,
114584             i = 0,
114585             features,
114586             len;
114587
114588         me.features = me.features || [];
114589         features = me.features;
114590         len = features.length;
114591
114592         me.featuresMC = Ext.create('Ext.util.MixedCollection');
114593         for (; i < len; i++) {
114594             // ensure feature hasnt already been instantiated
114595             if (!features[i].isFeature) {
114596                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
114597             }
114598             // inject a reference to view
114599             features[i].view = me;
114600             me.featuresMC.add(features[i]);
114601         }
114602     },
114603
114604     /**
114605      * Gives features an injection point to attach events to the markup that
114606      * has been created for this view.
114607      * @private
114608      */
114609     attachEventsForFeatures: function() {
114610         var features = this.features,
114611             ln       = features.length,
114612             i        = 0;
114613
114614         for (; i < ln; i++) {
114615             if (features[i].isFeature) {
114616                 features[i].attachEvents();
114617             }
114618         }
114619     },
114620
114621     afterRender: function() {
114622         var me = this;
114623
114624         me.callParent();
114625         me.mon(me.el, {
114626             scroll: me.fireBodyScroll,
114627             scope: me
114628         });
114629         me.el.unselectable();
114630         me.attachEventsForFeatures();
114631     },
114632
114633     fireBodyScroll: function(e, t) {
114634         this.fireEvent('bodyscroll', e, t);
114635     },
114636
114637     // TODO: Refactor headerCt dependency here to colModel
114638     /**
114639      * Uses the headerCt to transform data from dataIndex keys in a record to
114640      * headerId keys in each header and then run them through each feature to
114641      * get additional data for variables they have injected into the view template.
114642      * @private
114643      */
114644     prepareData: function(data, idx, record) {
114645         var me       = this,
114646             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
114647             features = me.features,
114648             ln       = features.length,
114649             i        = 0,
114650             node, feature;
114651
114652         for (; i < ln; i++) {
114653             feature = features[i];
114654             if (feature.isFeature) {
114655                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
114656             }
114657         }
114658
114659         return orig;
114660     },
114661
114662     // TODO: Refactor headerCt dependency here to colModel
114663     collectData: function(records, startIndex) {
114664         var preppedRecords = this.callParent(arguments),
114665             headerCt  = this.headerCt,
114666             fullWidth = headerCt.getFullWidth(),
114667             features  = this.features,
114668             ln = features.length,
114669             o = {
114670                 rows: preppedRecords,
114671                 fullWidth: fullWidth
114672             },
114673             i  = 0,
114674             feature,
114675             j = 0,
114676             jln,
114677             rowParams;
114678
114679         jln = preppedRecords.length;
114680         // process row classes, rowParams has been deprecated and has been moved
114681         // to the individual features that implement the behavior.
114682         if (this.getRowClass) {
114683             for (; j < jln; j++) {
114684                 rowParams = {};
114685                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
114686                 if (rowParams.alt) {
114687                     Ext.Error.raise("The getRowClass alt property is no longer supported.");
114688                 }
114689                 if (rowParams.tstyle) {
114690                     Ext.Error.raise("The getRowClass tstyle property is no longer supported.");
114691                 }
114692                 if (rowParams.cells) {
114693                     Ext.Error.raise("The getRowClass cells property is no longer supported.");
114694                 }
114695                 if (rowParams.body) {
114696                     Ext.Error.raise("The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.");
114697                 }
114698                 if (rowParams.bodyStyle) {
114699                     Ext.Error.raise("The getRowClass bodyStyle property is no longer supported.");
114700                 }
114701                 if (rowParams.cols) {
114702                     Ext.Error.raise("The getRowClass cols property is no longer supported.");
114703                 }
114704             }
114705         }
114706         // currently only one feature may implement collectData. This is to modify
114707         // what's returned to the view before its rendered
114708         for (; i < ln; i++) {
114709             feature = features[i];
114710             if (feature.isFeature && feature.collectData && !feature.disabled) {
114711                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
114712                 break;
114713             }
114714         }
114715         return o;
114716     },
114717
114718     // TODO: Refactor header resizing to column resizing
114719     /**
114720      * When a header is resized, setWidth on the individual columns resizer class,
114721      * the top level table, save/restore scroll state, generate a new template and
114722      * restore focus to the grid view's element so that keyboard navigation
114723      * continues to work.
114724      * @private
114725      */
114726     onHeaderResize: function(header, w, suppressFocus) {
114727         var me = this,
114728             el = me.el;
114729
114730         if (el) {
114731             me.saveScrollState();
114732             // Grab the col and set the width, css
114733             // class is generated in TableChunker.
114734             // Select composites because there may be several chunks.
114735
114736             // IE6 and IE7 bug.
114737             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
114738             // We need to increment the passed with in this case.
114739             if (Ext.isIE6 || Ext.isIE7) {
114740                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
114741                     w += 1;
114742                 }
114743             }
114744             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
114745             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
114746             me.restoreScrollState();
114747             if (!me.ignoreTemplate) {
114748                 me.setNewTemplate();
114749             }
114750             if (!suppressFocus) {
114751                 me.el.focus();
114752             }
114753         }
114754     },
114755
114756     /**
114757      * When a header is shown restore its oldWidth if it was previously hidden.
114758      * @private
114759      */
114760     onHeaderShow: function(headerCt, header, suppressFocus) {
114761         var me = this;
114762         me.ignoreTemplate = true;
114763         // restore headers that were dynamically hidden
114764         if (header.oldWidth) {
114765             me.onHeaderResize(header, header.oldWidth, suppressFocus);
114766             delete header.oldWidth;
114767         // flexed headers will have a calculated size set
114768         // this additional check has to do with the fact that
114769         // defaults: {width: 100} will fight with a flex value
114770         } else if (header.width && !header.flex) {
114771             me.onHeaderResize(header, header.width, suppressFocus);
114772         }
114773         delete me.ignoreTemplate;
114774         me.setNewTemplate();
114775     },
114776
114777     /**
114778      * When the header hides treat it as a resize to 0.
114779      * @private
114780      */
114781     onHeaderHide: function(headerCt, header, suppressFocus) {
114782         this.onHeaderResize(header, 0, suppressFocus);
114783     },
114784
114785     /**
114786      * Set a new template based on the current columns displayed in the
114787      * grid.
114788      * @private
114789      */
114790     setNewTemplate: function() {
114791         var me = this,
114792             columns = me.headerCt.getColumnsForTpl(true);
114793
114794         me.tpl = me.getTableChunker().getTableTpl({
114795             columns: columns,
114796             features: me.features
114797         });
114798     },
114799
114800     /**
114801      * Returns the configured chunker or default of Ext.view.TableChunker
114802      */
114803     getTableChunker: function() {
114804         return this.chunker || Ext.view.TableChunker;
114805     },
114806
114807     /**
114808      * Adds a CSS Class to a specific row.
114809      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114810      * representing this row
114811      * @param {String} cls
114812      */
114813     addRowCls: function(rowInfo, cls) {
114814         var row = this.getNode(rowInfo);
114815         if (row) {
114816             Ext.fly(row).addCls(cls);
114817         }
114818     },
114819
114820     /**
114821      * Removes a CSS Class from a specific row.
114822      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114823      * representing this row
114824      * @param {String} cls
114825      */
114826     removeRowCls: function(rowInfo, cls) {
114827         var row = this.getNode(rowInfo);
114828         if (row) {
114829             Ext.fly(row).removeCls(cls);
114830         }
114831     },
114832
114833     // GridSelectionModel invokes onRowSelect as selection changes
114834     onRowSelect : function(rowIdx) {
114835         this.addRowCls(rowIdx, this.selectedItemCls);
114836     },
114837
114838     // GridSelectionModel invokes onRowDeselect as selection changes
114839     onRowDeselect : function(rowIdx) {
114840         var me = this;
114841
114842         me.removeRowCls(rowIdx, me.selectedItemCls);
114843         me.removeRowCls(rowIdx, me.focusedItemCls);
114844     },
114845
114846     onCellSelect: function(position) {
114847         var cell = this.getCellByPosition(position);
114848         if (cell) {
114849             cell.addCls(this.selectedCellCls);
114850         }
114851     },
114852
114853     onCellDeselect: function(position) {
114854         var cell = this.getCellByPosition(position);
114855         if (cell) {
114856             cell.removeCls(this.selectedCellCls);
114857         }
114858
114859     },
114860
114861     onCellFocus: function(position) {
114862         //var cell = this.getCellByPosition(position);
114863         this.focusCell(position);
114864     },
114865
114866     getCellByPosition: function(position) {
114867         var row    = position.row,
114868             column = position.column,
114869             store  = this.store,
114870             node   = this.getNode(row),
114871             header = this.headerCt.getHeaderAtIndex(column),
114872             cellSelector,
114873             cell = false;
114874
114875         if (header && node) {
114876             cellSelector = header.getCellSelector();
114877             cell = Ext.fly(node).down(cellSelector);
114878         }
114879         return cell;
114880     },
114881
114882     // GridSelectionModel invokes onRowFocus to 'highlight'
114883     // the last row focused
114884     onRowFocus: function(rowIdx, highlight, supressFocus) {
114885         var me = this,
114886             row = me.getNode(rowIdx);
114887
114888         if (highlight) {
114889             me.addRowCls(rowIdx, me.focusedItemCls);
114890             if (!supressFocus) {
114891                 me.focusRow(rowIdx);
114892             }
114893             //this.el.dom.setAttribute('aria-activedescendant', row.id);
114894         } else {
114895             me.removeRowCls(rowIdx, me.focusedItemCls);
114896         }
114897     },
114898
114899     /**
114900      * Focuses a particular row and brings it into view. Will fire the rowfocus event.
114901      * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
114902      * An HTMLElement template node, index of a template node, the id of a template node or the
114903      * record associated with the node.
114904      */
114905     focusRow: function(rowIdx) {
114906         var me         = this,
114907             row        = me.getNode(rowIdx),
114908             el         = me.el,
114909             adjustment = 0,
114910             panel      = me.ownerCt,
114911             rowRegion,
114912             elRegion,
114913             record;
114914
114915         if (row && el) {
114916             elRegion  = el.getRegion();
114917             rowRegion = Ext.fly(row).getRegion();
114918             // row is above
114919             if (rowRegion.top < elRegion.top) {
114920                 adjustment = rowRegion.top - elRegion.top;
114921             // row is below
114922             } else if (rowRegion.bottom > elRegion.bottom) {
114923                 adjustment = rowRegion.bottom - elRegion.bottom;
114924             }
114925             record = me.getRecord(row);
114926             rowIdx = me.store.indexOf(record);
114927
114928             if (adjustment) {
114929                 // scroll the grid itself, so that all gridview's update.
114930                 panel.scrollByDeltaY(adjustment);
114931             }
114932             me.fireEvent('rowfocus', record, row, rowIdx);
114933         }
114934     },
114935
114936     focusCell: function(position) {
114937         var me          = this,
114938             cell        = me.getCellByPosition(position),
114939             el          = me.el,
114940             adjustmentY = 0,
114941             adjustmentX = 0,
114942             elRegion    = el.getRegion(),
114943             panel       = me.ownerCt,
114944             cellRegion,
114945             record;
114946
114947         if (cell) {
114948             cellRegion = cell.getRegion();
114949             // cell is above
114950             if (cellRegion.top < elRegion.top) {
114951                 adjustmentY = cellRegion.top - elRegion.top;
114952             // cell is below
114953             } else if (cellRegion.bottom > elRegion.bottom) {
114954                 adjustmentY = cellRegion.bottom - elRegion.bottom;
114955             }
114956
114957             // cell is left
114958             if (cellRegion.left < elRegion.left) {
114959                 adjustmentX = cellRegion.left - elRegion.left;
114960             // cell is right
114961             } else if (cellRegion.right > elRegion.right) {
114962                 adjustmentX = cellRegion.right - elRegion.right;
114963             }
114964
114965             if (adjustmentY) {
114966                 // scroll the grid itself, so that all gridview's update.
114967                 panel.scrollByDeltaY(adjustmentY);
114968             }
114969             if (adjustmentX) {
114970                 panel.scrollByDeltaX(adjustmentX);
114971             }
114972             el.focus();
114973             me.fireEvent('cellfocus', record, cell, position);
114974         }
114975     },
114976
114977     /**
114978      * Scrolls by delta. This affects this individual view ONLY and does not
114979      * synchronize across views or scrollers.
114980      * @param {Number} delta
114981      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
114982      * @private
114983      */
114984     scrollByDelta: function(delta, dir) {
114985         dir = dir || 'scrollTop';
114986         var elDom = this.el.dom;
114987         elDom[dir] = (elDom[dir] += delta);
114988     },
114989
114990     onUpdate: function(ds, index) {
114991         this.callParent(arguments);
114992     },
114993
114994     /**
114995      * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState
114996      */
114997     saveScrollState: function() {
114998         if (this.rendered) {
114999             var dom = this.el.dom, 
115000                 state = this.scrollState;
115001             
115002             state.left = dom.scrollLeft;
115003             state.top = dom.scrollTop;
115004         }
115005     },
115006
115007     /**
115008      * Restores the scrollState.
115009      * Must be used in conjunction with saveScrollState
115010      * @private
115011      */
115012     restoreScrollState: function() {
115013         if (this.rendered) {
115014             var dom = this.el.dom, 
115015                 state = this.scrollState, 
115016                 headerEl = this.headerCt.el.dom;
115017             
115018             headerEl.scrollLeft = dom.scrollLeft = state.left;
115019             dom.scrollTop = state.top;
115020         }
115021     },
115022
115023     /**
115024      * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
115025      * invalidates the scrollers.
115026      */
115027     refresh: function() {
115028         this.setNewTemplate();
115029         this.callParent(arguments);
115030     },
115031
115032     processItemEvent: function(record, row, rowIndex, e) {
115033         var me = this,
115034             cell = e.getTarget(me.cellSelector, row),
115035             cellIndex = cell ? cell.cellIndex : -1,
115036             map = me.statics().EventMap,
115037             selModel = me.getSelectionModel(),
115038             type = e.type,
115039             result;
115040
115041         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
115042             // CellModel, otherwise we can't tell which cell to invoke
115043             cell = me.getCellByPosition(selModel.getCurrentPosition());
115044             if (cell) {
115045                 cell = cell.dom;
115046                 cellIndex = cell.cellIndex;
115047             }
115048         }
115049
115050         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
115051
115052         if (result === false || me.callParent(arguments) === false) {
115053             return false;
115054         }
115055
115056         // Don't handle cellmouseenter and cellmouseleave events for now
115057         if (type == 'mouseover' || type == 'mouseout') {
115058             return true;
115059         }
115060
115061         return !(
115062             // We are adding cell and feature events
115063             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
115064             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
115065             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
115066             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
115067         );
115068     },
115069
115070     processSpecialEvent: function(e) {
115071         var me = this,
115072             map = me.statics().EventMap,
115073             features = me.features,
115074             ln = features.length,
115075             type = e.type,
115076             i, feature, prefix, featureTarget,
115077             beforeArgs, args,
115078             panel = me.ownerCt;
115079
115080         me.callParent(arguments);
115081
115082         if (type == 'mouseover' || type == 'mouseout') {
115083             return;
115084         }
115085
115086         for (i = 0; i < ln; i++) {
115087             feature = features[i];
115088             if (feature.hasFeatureEvent) {
115089                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
115090                 if (featureTarget) {
115091                     prefix = feature.eventPrefix;
115092                     // allows features to implement getFireEventArgs to change the
115093                     // fireEvent signature
115094                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
115095                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
115096
115097                     if (
115098                         // before view event
115099                         (me.fireEvent.apply(me, beforeArgs) === false) ||
115100                         // panel grid event
115101                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
115102                         // view event
115103                         (me.fireEvent.apply(me, args) === false) ||
115104                         // panel event
115105                         (panel.fireEvent.apply(panel, args) === false)
115106                     ) {
115107                         return false;
115108                     }
115109                 }
115110             }
115111         }
115112         return true;
115113     },
115114
115115     onCellMouseDown: Ext.emptyFn,
115116     onCellMouseUp: Ext.emptyFn,
115117     onCellClick: Ext.emptyFn,
115118     onCellDblClick: Ext.emptyFn,
115119     onCellContextMenu: Ext.emptyFn,
115120     onCellKeyDown: Ext.emptyFn,
115121     onBeforeCellMouseDown: Ext.emptyFn,
115122     onBeforeCellMouseUp: Ext.emptyFn,
115123     onBeforeCellClick: Ext.emptyFn,
115124     onBeforeCellDblClick: Ext.emptyFn,
115125     onBeforeCellContextMenu: Ext.emptyFn,
115126     onBeforeCellKeyDown: Ext.emptyFn,
115127
115128     /**
115129      * Expands a particular header to fit the max content width.
115130      * This will ONLY expand, not contract.
115131      * @private
115132      */
115133     expandToFit: function(header) {
115134         if (header) {
115135             var maxWidth = this.getMaxContentWidth(header);
115136             delete header.flex;
115137             header.setWidth(maxWidth);
115138         }
115139     },
115140
115141     /**
115142      * Returns the max contentWidth of the header's text and all cells
115143      * in the grid under this header.
115144      * @private
115145      */
115146     getMaxContentWidth: function(header) {
115147         var cellSelector = header.getCellInnerSelector(),
115148             cells        = this.el.query(cellSelector),
115149             i = 0,
115150             ln = cells.length,
115151             maxWidth = header.el.dom.scrollWidth,
115152             scrollWidth;
115153
115154         for (; i < ln; i++) {
115155             scrollWidth = cells[i].scrollWidth;
115156             if (scrollWidth > maxWidth) {
115157                 maxWidth = scrollWidth;
115158             }
115159         }
115160         return maxWidth;
115161     },
115162
115163     getPositionByEvent: function(e) {
115164         var me       = this,
115165             cellNode = e.getTarget(me.cellSelector),
115166             rowNode  = e.getTarget(me.itemSelector),
115167             record   = me.getRecord(rowNode),
115168             header   = me.getHeaderByCell(cellNode);
115169
115170         return me.getPosition(record, header);
115171     },
115172
115173     getHeaderByCell: function(cell) {
115174         if (cell) {
115175             var m = cell.className.match(this.cellRe);
115176             if (m && m[1]) {
115177                 return Ext.getCmp(m[1]);
115178             }
115179         }
115180         return false;
115181     },
115182
115183     /**
115184      * @param {Object} position The current row and column: an object containing the following properties:
115185      *
115186      * - row - The row index
115187      * - column - The column index
115188      *
115189      * @param {String} direction 'up', 'down', 'right' and 'left'
115190      * @param {Ext.EventObject} e event
115191      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
115192      * @param {Function} verifierFn A function to verify the validity of the calculated position.
115193      * When using this function, you must return true to allow the newPosition to be returned.
115194      * @param {Object} scope Scope to run the verifierFn in
115195      * @returns {Object} newPosition An object containing the following properties:
115196      *
115197      * - row - The row index
115198      * - column - The column index
115199      *
115200      * @private
115201      */
115202     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
115203         var me       = this,
115204             row      = pos.row,
115205             column   = pos.column,
115206             rowCount = me.store.getCount(),
115207             firstCol = me.getFirstVisibleColumnIndex(),
115208             lastCol  = me.getLastVisibleColumnIndex(),
115209             newPos   = {row: row, column: column},
115210             activeHeader = me.headerCt.getHeaderAtIndex(column);
115211
115212         // no active header or its currently hidden
115213         if (!activeHeader || activeHeader.hidden) {
115214             return false;
115215         }
115216
115217         e = e || {};
115218         direction = direction.toLowerCase();
115219         switch (direction) {
115220             case 'right':
115221                 // has the potential to wrap if its last
115222                 if (column === lastCol) {
115223                     // if bottom row and last column, deny right
115224                     if (preventWrap || row === rowCount - 1) {
115225                         return false;
115226                     }
115227                     if (!e.ctrlKey) {
115228                         // otherwise wrap to nextRow and firstCol
115229                         newPos.row = row + 1;
115230                         newPos.column = firstCol;
115231                     }
115232                 // go right
115233                 } else {
115234                     if (!e.ctrlKey) {
115235                         newPos.column = column + me.getRightGap(activeHeader);
115236                     } else {
115237                         newPos.column = lastCol;
115238                     }
115239                 }
115240                 break;
115241
115242             case 'left':
115243                 // has the potential to wrap
115244                 if (column === firstCol) {
115245                     // if top row and first column, deny left
115246                     if (preventWrap || row === 0) {
115247                         return false;
115248                     }
115249                     if (!e.ctrlKey) {
115250                         // otherwise wrap to prevRow and lastCol
115251                         newPos.row = row - 1;
115252                         newPos.column = lastCol;
115253                     }
115254                 // go left
115255                 } else {
115256                     if (!e.ctrlKey) {
115257                         newPos.column = column + me.getLeftGap(activeHeader);
115258                     } else {
115259                         newPos.column = firstCol;
115260                     }
115261                 }
115262                 break;
115263
115264             case 'up':
115265                 // if top row, deny up
115266                 if (row === 0) {
115267                     return false;
115268                 // go up
115269                 } else {
115270                     if (!e.ctrlKey) {
115271                         newPos.row = row - 1;
115272                     } else {
115273                         newPos.row = 0;
115274                     }
115275                 }
115276                 break;
115277
115278             case 'down':
115279                 // if bottom row, deny down
115280                 if (row === rowCount - 1) {
115281                     return false;
115282                 // go down
115283                 } else {
115284                     if (!e.ctrlKey) {
115285                         newPos.row = row + 1;
115286                     } else {
115287                         newPos.row = rowCount - 1;
115288                     }
115289                 }
115290                 break;
115291         }
115292
115293         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
115294             return false;
115295         } else {
115296             return newPos;
115297         }
115298     },
115299     getFirstVisibleColumnIndex: function() {
115300         var headerCt   = this.getHeaderCt(),
115301             allColumns = headerCt.getGridColumns(),
115302             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
115303             firstHeader = visHeaders[0];
115304
115305         return headerCt.getHeaderIndex(firstHeader);
115306     },
115307
115308     getLastVisibleColumnIndex: function() {
115309         var headerCt   = this.getHeaderCt(),
115310             allColumns = headerCt.getGridColumns(),
115311             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
115312             lastHeader = visHeaders[visHeaders.length - 1];
115313
115314         return headerCt.getHeaderIndex(lastHeader);
115315     },
115316
115317     getHeaderCt: function() {
115318         return this.headerCt;
115319     },
115320
115321     getPosition: function(record, header) {
115322         var me = this,
115323             store = me.store,
115324             gridCols = me.headerCt.getGridColumns();
115325
115326         return {
115327             row: store.indexOf(record),
115328             column: Ext.Array.indexOf(gridCols, header)
115329         };
115330     },
115331
115332     /**
115333      * Determines the 'gap' between the closest adjacent header to the right
115334      * that is not hidden.
115335      * @private
115336      */
115337     getRightGap: function(activeHeader) {
115338         var headerCt        = this.getHeaderCt(),
115339             headers         = headerCt.getGridColumns(),
115340             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
115341             i               = activeHeaderIdx + 1,
115342             nextIdx;
115343
115344         for (; i <= headers.length; i++) {
115345             if (!headers[i].hidden) {
115346                 nextIdx = i;
115347                 break;
115348             }
115349         }
115350
115351         return nextIdx - activeHeaderIdx;
115352     },
115353
115354     beforeDestroy: function() {
115355         if (this.rendered) {
115356             this.el.removeAllListeners();
115357         }
115358         this.callParent(arguments);
115359     },
115360
115361     /**
115362      * Determines the 'gap' between the closest adjacent header to the left
115363      * that is not hidden.
115364      * @private
115365      */
115366     getLeftGap: function(activeHeader) {
115367         var headerCt        = this.getHeaderCt(),
115368             headers         = headerCt.getGridColumns(),
115369             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
115370             i               = activeHeaderIdx - 1,
115371             prevIdx;
115372
115373         for (; i >= 0; i--) {
115374             if (!headers[i].hidden) {
115375                 prevIdx = i;
115376                 break;
115377             }
115378         }
115379
115380         return prevIdx - activeHeaderIdx;
115381     }
115382 });
115383 /**
115384  * @class Ext.grid.View
115385  * @extends Ext.view.Table
115386  *
115387  * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
115388  * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
115389  * option is passed to the grid:
115390  *
115391  *     Ext.create('Ext.grid.Panel', {
115392  *         // other options
115393  *         viewConfig: {
115394  *             stripeRows: false
115395  *         }
115396  *     });
115397  *
115398  * ## Drag Drop
115399  *
115400  * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
115401  * when creating the view.
115402  *
115403  *     Ext.create('Ext.grid.Panel', {
115404  *         // other options
115405  *         viewConfig: {
115406  *             plugins: {
115407  *                 ddGroup: 'people-group',
115408  *                 ptype: 'gridviewdragdrop',
115409  *                 enableDrop: false
115410  *             }
115411  *         }
115412  *     });
115413  */
115414 Ext.define('Ext.grid.View', {
115415     extend: 'Ext.view.Table',
115416     alias: 'widget.gridview',
115417
115418     /**
115419      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>true</tt>.
115420      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
115421      * the grid. A default CSS rule is provided which sets a background color, but you can override this
115422      * with a rule which either overrides the <b>background-color</b> style using the '!important'
115423      * modifier, or which uses a CSS selector of higher specificity.</p>
115424      */
115425     stripeRows: true,
115426
115427     invalidateScrollerOnRefresh: true,
115428
115429     /**
115430      * Scroll the GridView to the top by scrolling the scroller.
115431      * @private
115432      */
115433     scrollToTop : function(){
115434         if (this.rendered) {
115435             var section = this.ownerCt,
115436                 verticalScroller = section.verticalScroller;
115437
115438             if (verticalScroller) {
115439                 verticalScroller.scrollToTop();
115440             }
115441         }
115442     },
115443
115444     // after adding a row stripe rows from then on
115445     onAdd: function(ds, records, index) {
115446         this.callParent(arguments);
115447         this.doStripeRows(index);
115448     },
115449
115450     // after removing a row stripe rows from then on
115451     onRemove: function(ds, records, index) {
115452         this.callParent(arguments);
115453         this.doStripeRows(index);
115454     },
115455
115456     onUpdate: function(ds, record, operation) {
115457         var index = ds.indexOf(record);
115458         this.callParent(arguments);
115459         this.doStripeRows(index, index);
115460     },
115461
115462     /**
115463      * Stripe rows from a particular row index
115464      * @param {Number} startRow
115465      * @param {Number} endRow (Optional) argument specifying the last row to process. By default process up to the last row.
115466      * @private
115467      */
115468     doStripeRows: function(startRow, endRow) {
115469         // ensure stripeRows configuration is turned on
115470         if (this.stripeRows) {
115471             var rows   = this.getNodes(startRow, endRow),
115472                 rowsLn = rows.length,
115473                 i      = 0,
115474                 row;
115475
115476             for (; i < rowsLn; i++) {
115477                 row = rows[i];
115478                 // Remove prior applied row classes.
115479                 row.className = row.className.replace(this.rowClsRe, ' ');
115480                 startRow++;
115481                 // Every odd row will get an additional cls
115482                 if (startRow % 2 === 0) {
115483                     row.className += (' ' + this.altRowCls);
115484                 }
115485             }
115486         }
115487     },
115488
115489     refresh: function(firstPass) {
115490         this.callParent(arguments);
115491         this.doStripeRows(0);
115492         // TODO: Remove gridpanel dependency
115493         var g = this.up('gridpanel');
115494         if (g && this.invalidateScrollerOnRefresh) {
115495             g.invalidateScroller();
115496         }
115497     }
115498 });
115499
115500 /**
115501  * @author Aaron Conran
115502  * @docauthor Ed Spencer
115503  *
115504  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
115505  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
115506  *
115507  * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
115508  *
115509  * ## Basic GridPanel
115510  *
115511  *     @example
115512  *     Ext.create('Ext.data.Store', {
115513  *         storeId:'simpsonsStore',
115514  *         fields:['name', 'email', 'phone'],
115515  *         data:{'items':[
115516  *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
115517  *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
115518  *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
115519  *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
115520  *         ]},
115521  *         proxy: {
115522  *             type: 'memory',
115523  *             reader: {
115524  *                 type: 'json',
115525  *                 root: 'items'
115526  *             }
115527  *         }
115528  *     });
115529  *
115530  *     Ext.create('Ext.grid.Panel', {
115531  *         title: 'Simpsons',
115532  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115533  *         columns: [
115534  *             { header: 'Name',  dataIndex: 'name' },
115535  *             { header: 'Email', dataIndex: 'email', flex: 1 },
115536  *             { header: 'Phone', dataIndex: 'phone' }
115537  *         ],
115538  *         height: 200,
115539  *         width: 400,
115540  *         renderTo: Ext.getBody()
115541  *     });
115542  *
115543  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
115544  * In most apps we would be placing the grid inside another container and wouldn't need to use the
115545  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
115546  * up and running.
115547  *
115548  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
115549  * and finally the grid rows under the headers.
115550  *
115551  * ## Configuring columns
115552  *
115553  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
115554  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
115555  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
115556  *
115557  *     columns: [
115558  *         {
115559  *             header: 'Name',
115560  *             dataIndex: 'name',
115561  *             sortable: false,
115562  *             hideable: false,
115563  *             flex: 1
115564  *         },
115565  *         {
115566  *             header: 'Email',
115567  *             dataIndex: 'email',
115568  *             hidden: true
115569  *         },
115570  *         {
115571  *             header: 'Phone',
115572  *             dataIndex: 'phone',
115573  *             width: 100
115574  *         }
115575  *     ]
115576  *
115577  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
115578  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
115579  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
115580  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
115581  *
115582  * ## Renderers
115583  *
115584  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
115585  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
115586  * we could define a renderer function for the email column to turn each email address into a mailto link:
115587  *
115588  *     columns: [
115589  *         {
115590  *             header: 'Email',
115591  *             dataIndex: 'email',
115592  *             renderer: function(value) {
115593  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
115594  *             }
115595  *         }
115596  *     ]
115597  *
115598  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
115599  *
115600  * ## Selection Models
115601  *
115602  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
115603  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
115604  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
115605  * CellSelectionModel, where individual cells are selected.
115606  *
115607  * Grids use a Row Selection Model by default, but this is easy to customise like so:
115608  *
115609  *     Ext.create('Ext.grid.Panel', {
115610  *         selType: 'cellmodel',
115611  *         store: ...
115612  *     });
115613  *
115614  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
115615  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
115616  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
115617  * conjunction with editing.
115618  *
115619  * ## Editing
115620  *
115621  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
115622  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
115623  * on both the name and the email columns:
115624  *
115625  *     Ext.create('Ext.grid.Panel', {
115626  *         title: 'Simpsons',
115627  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115628  *         columns: [
115629  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
115630  *             { header: 'Email', dataIndex: 'email', flex: 1,
115631  *                 field: {
115632  *                     xtype: 'textfield',
115633  *                     allowBlank: false
115634  *                 }
115635  *             },
115636  *             { header: 'Phone', dataIndex: 'phone' }
115637  *         ],
115638  *         selType: 'cellmodel',
115639  *         plugins: [
115640  *             Ext.create('Ext.grid.plugin.CellEditing', {
115641  *                 clicksToEdit: 1
115642  *             })
115643  *         ],
115644  *         height: 200,
115645  *         width: 400,
115646  *         renderTo: Ext.getBody()
115647  *     });
115648  *
115649  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but
115650  * this time we've also specified a {@link Ext.grid.column.Column#field field} on two of our columns. For the Name column
115651  * we just want a default textfield to edit the value, so we specify 'textfield'. For the Email column we customized the
115652  * editor slightly by passing allowBlank: false, which will provide inline validation.
115653  *
115654  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
115655  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
115656  * single click.
115657  *
115658  * ## Row Editing
115659  *
115660  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
115661  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
115662  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
115663  *
115664  *     Ext.create('Ext.grid.Panel', {
115665  *         title: 'Simpsons',
115666  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115667  *         columns: [
115668  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
115669  *             { header: 'Email', dataIndex: 'email', flex:1,
115670  *                 field: {
115671  *                     xtype: 'textfield',
115672  *                     allowBlank: false
115673  *                 }
115674  *             },
115675  *             { header: 'Phone', dataIndex: 'phone' }
115676  *         ],
115677  *         selType: 'rowmodel',
115678  *         plugins: [
115679  *             Ext.create('Ext.grid.plugin.RowEditing', {
115680  *                 clicksToEdit: 1
115681  *             })
115682  *         ],
115683  *         height: 200,
115684  *         width: 400,
115685  *         renderTo: Ext.getBody()
115686  *     });
115687  *
115688  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
115689  * editor will appear and enable us to edit each of the columns we have specified an editor for.
115690  *
115691  * ## Sorting & Filtering
115692  *
115693  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
115694  * easy to set up a grid to be sorted from the start:
115695  *
115696  *     var myGrid = Ext.create('Ext.grid.Panel', {
115697  *         store: {
115698  *             fields: ['name', 'email', 'phone'],
115699  *             sorters: ['name', 'phone']
115700  *         },
115701  *         columns: [
115702  *             { text: 'Name',  dataIndex: 'name' },
115703  *             { text: 'Email', dataIndex: 'email' }
115704  *         ]
115705  *     });
115706  *
115707  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
115708  * more than one field at run time it's easy to do so by adding new sorters to the store:
115709  *
115710  *     myGrid.store.sort([
115711  *         { property: 'name',  direction: 'ASC' },
115712  *         { property: 'email', direction: 'DESC' }
115713  *     ]);
115714  *
115715  * See {@link Ext.data.Store} for examples of filtering.
115716  *
115717  * ## Grouping
115718  *
115719  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to
115720  * group by the department that each employee works in. Here's how we might set that up:
115721  *
115722  *     @example
115723  *     var store = Ext.create('Ext.data.Store', {
115724  *         storeId:'employeeStore',
115725  *         fields:['name', 'senority', 'department'],
115726  *         groupField: 'department',
115727  *         data: {'employees':[
115728  *             { "name": "Michael Scott",  "senority": 7, "department": "Manangement" },
115729  *             { "name": "Dwight Schrute", "senority": 2, "department": "Sales" },
115730  *             { "name": "Jim Halpert",    "senority": 3, "department": "Sales" },
115731  *             { "name": "Kevin Malone",   "senority": 4, "department": "Accounting" },
115732  *             { "name": "Angela Martin",  "senority": 5, "department": "Accounting" }
115733  *         ]},
115734  *         proxy: {
115735  *             type: 'memory',
115736  *             reader: {
115737  *                 type: 'json',
115738  *                 root: 'employees'
115739  *             }
115740  *         }
115741  *     });
115742  *
115743  *     Ext.create('Ext.grid.Panel', {
115744  *         title: 'Employees',
115745  *         store: Ext.data.StoreManager.lookup('employeeStore'),
115746  *         columns: [
115747  *             { header: 'Name',     dataIndex: 'name' },
115748  *             { header: 'Senority', dataIndex: 'senority' }
115749  *         ],
115750  *         features: [{ftype:'grouping'}],
115751  *         width: 200,
115752  *         height: 275,
115753  *         renderTo: Ext.getBody()
115754  *     });
115755  *
115756  * ## Infinite Scrolling
115757  *
115758  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
115759  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
115760  * to a store with a pageSize specified.
115761  *
115762  *     var grid = Ext.create('Ext.grid.Panel', {
115763  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
115764  *         verticalScrollerType: 'paginggridscroller',
115765  *         // do not reset the scrollbar when the view refreshs
115766  *         invalidateScrollerOnRefresh: false,
115767  *         // infinite scrolling does not support selection
115768  *         disableSelection: true,
115769  *         // ...
115770  *     });
115771  *
115772  * ## Paging
115773  *
115774  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
115775  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
115776  *
115777  *     @example
115778  *     var itemsPerPage = 2;   // set the number of items you want per page
115779  *
115780  *     var store = Ext.create('Ext.data.Store', {
115781  *         id:'simpsonsStore',
115782  *         autoLoad: false,
115783  *         fields:['name', 'email', 'phone'],
115784  *         pageSize: itemsPerPage, // items per page
115785  *         proxy: {
115786  *             type: 'ajax',
115787  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
115788  *             reader: {
115789  *                 type: 'json',
115790  *                 root: 'items',
115791  *                 totalProperty: 'total'
115792  *             }
115793  *         }
115794  *     });
115795  *
115796  *     // specify segment of data you want to load using params
115797  *     store.load({
115798  *         params:{
115799  *             start:0,
115800  *             limit: itemsPerPage
115801  *         }
115802  *     });
115803  *
115804  *     Ext.create('Ext.grid.Panel', {
115805  *         title: 'Simpsons',
115806  *         store: store,
115807  *         columns: [
115808  *             {header: 'Name',  dataIndex: 'name'},
115809  *             {header: 'Email', dataIndex: 'email', flex:1},
115810  *             {header: 'Phone', dataIndex: 'phone'}
115811  *         ],
115812  *         width: 400,
115813  *         height: 125,
115814  *         dockedItems: [{
115815  *             xtype: 'pagingtoolbar',
115816  *             store: store,   // same store GridPanel is using
115817  *             dock: 'bottom',
115818  *             displayInfo: true
115819  *         }],
115820  *         renderTo: Ext.getBody()
115821  *     });
115822  */
115823 Ext.define('Ext.grid.Panel', {
115824     extend: 'Ext.panel.Table',
115825     requires: ['Ext.grid.View'],
115826     alias: ['widget.gridpanel', 'widget.grid'],
115827     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
115828     viewType: 'gridview',
115829
115830     lockable: false,
115831
115832     // Required for the Lockable Mixin. These are the configurations which will be copied to the
115833     // normal and locked sub tablepanels
115834     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
115835     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
115836
115837     /**
115838      * @cfg {Boolean} [columnLines=false] Adds column line styling
115839      */
115840
115841     initComponent: function() {
115842         var me = this;
115843
115844         if (me.columnLines) {
115845             me.setColumnLines(me.columnLines);
115846         }
115847
115848         me.callParent();
115849     },
115850
115851     setColumnLines: function(show) {
115852         var me = this,
115853             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
115854
115855         me[method]('with-col-lines');
115856     }
115857 });
115858
115859 // Currently has the following issues:
115860 // - Does not handle postEditValue
115861 // - Fields without editors need to sync with their values in Store
115862 // - starting to edit another record while already editing and dirty should probably prevent it
115863 // - aggregating validation messages
115864 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
115865 // - layout issues when changing sizes/width while hidden (layout bug)
115866
115867 /**
115868  * @class Ext.grid.RowEditor
115869  * @extends Ext.form.Panel
115870  *
115871  * Internal utility class used to provide row editing functionality. For developers, they should use
115872  * the RowEditing plugin to use this functionality with a grid.
115873  *
115874  * @ignore
115875  */
115876 Ext.define('Ext.grid.RowEditor', {
115877     extend: 'Ext.form.Panel',
115878     requires: [
115879         'Ext.tip.ToolTip',
115880         'Ext.util.HashMap',
115881         'Ext.util.KeyNav'
115882     ],
115883
115884     saveBtnText  : 'Update',
115885     cancelBtnText: 'Cancel',
115886     errorsText: 'Errors',
115887     dirtyText: 'You need to commit or cancel your changes',
115888
115889     lastScrollLeft: 0,
115890     lastScrollTop: 0,
115891
115892     border: false,
115893     
115894     // Change the hideMode to offsets so that we get accurate measurements when
115895     // the roweditor is hidden for laying out things like a TriggerField.
115896     hideMode: 'offsets',
115897
115898     initComponent: function() {
115899         var me = this,
115900             form;
115901
115902         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
115903
115904         me.layout = {
115905             type: 'hbox',
115906             align: 'middle'
115907         };
115908
115909         // Maintain field-to-column mapping
115910         // It's easy to get a field from a column, but not vice versa
115911         me.columns = Ext.create('Ext.util.HashMap');
115912         me.columns.getKey = function(columnHeader) {
115913             var f;
115914             if (columnHeader.getEditor) {
115915                 f = columnHeader.getEditor();
115916                 if (f) {
115917                     return f.id;
115918                 }
115919             }
115920             return columnHeader.id;
115921         };
115922         me.mon(me.columns, {
115923             add: me.onFieldAdd,
115924             remove: me.onFieldRemove,
115925             replace: me.onFieldReplace,
115926             scope: me
115927         });
115928
115929         me.callParent(arguments);
115930
115931         if (me.fields) {
115932             me.setField(me.fields);
115933             delete me.fields;
115934         }
115935
115936         form = me.getForm();
115937         form.trackResetOnLoad = true;
115938     },
115939
115940     onFieldChange: function() {
115941         var me = this,
115942             form = me.getForm(),
115943             valid = form.isValid();
115944         if (me.errorSummary && me.isVisible()) {
115945             me[valid ? 'hideToolTip' : 'showToolTip']();
115946         }
115947         if (me.floatingButtons) {
115948             me.floatingButtons.child('#update').setDisabled(!valid);
115949         }
115950         me.isValid = valid;
115951     },
115952
115953     afterRender: function() {
115954         var me = this,
115955             plugin = me.editingPlugin;
115956
115957         me.callParent(arguments);
115958         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
115959
115960         // Prevent from bubbling click events to the grid view
115961         me.mon(me.el, {
115962             click: Ext.emptyFn,
115963             stopPropagation: true
115964         });
115965
115966         me.el.swallowEvent([
115967             'keypress',
115968             'keydown'
115969         ]);
115970
115971         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
115972             enter: plugin.completeEdit,
115973             esc: plugin.onEscKey,
115974             scope: plugin
115975         });
115976
115977         me.mon(plugin.view, {
115978             beforerefresh: me.onBeforeViewRefresh,
115979             refresh: me.onViewRefresh,
115980             scope: me
115981         });
115982     },
115983
115984     onBeforeViewRefresh: function(view) {
115985         var me = this,
115986             viewDom = view.el.dom;
115987
115988         if (me.el.dom.parentNode === viewDom) {
115989             viewDom.removeChild(me.el.dom);
115990         }
115991     },
115992
115993     onViewRefresh: function(view) {
115994         var me = this,
115995             viewDom = view.el.dom,
115996             context = me.context,
115997             idx;
115998
115999         viewDom.appendChild(me.el.dom);
116000
116001         // Recover our row node after a view refresh
116002         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
116003             context.row = view.getNode(idx);
116004             me.reposition();
116005             if (me.tooltip && me.tooltip.isVisible()) {
116006                 me.tooltip.setTarget(context.row);
116007             }
116008         } else {
116009             me.editingPlugin.cancelEdit();
116010         }
116011     },
116012
116013     onCtScroll: function(e, target) {
116014         var me = this,
116015             scrollTop  = target.scrollTop,
116016             scrollLeft = target.scrollLeft;
116017
116018         if (scrollTop !== me.lastScrollTop) {
116019             me.lastScrollTop = scrollTop;
116020             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
116021                 me.repositionTip();
116022             }
116023         }
116024         if (scrollLeft !== me.lastScrollLeft) {
116025             me.lastScrollLeft = scrollLeft;
116026             me.reposition();
116027         }
116028     },
116029
116030     onColumnAdd: function(column) {
116031         this.setField(column);
116032     },
116033
116034     onColumnRemove: function(column) {
116035         this.columns.remove(column);
116036     },
116037
116038     onColumnResize: function(column, width) {
116039         column.getEditor().setWidth(width - 2);
116040         if (this.isVisible()) {
116041             this.reposition();
116042         }
116043     },
116044
116045     onColumnHide: function(column) {
116046         column.getEditor().hide();
116047         if (this.isVisible()) {
116048             this.reposition();
116049         }
116050     },
116051
116052     onColumnShow: function(column) {
116053         var field = column.getEditor();
116054         field.setWidth(column.getWidth() - 2).show();
116055         if (this.isVisible()) {
116056             this.reposition();
116057         }
116058     },
116059
116060     onColumnMove: function(column, fromIdx, toIdx) {
116061         var field = column.getEditor();
116062         if (this.items.indexOf(field) != toIdx) {
116063             this.move(fromIdx, toIdx);
116064         }
116065     },
116066
116067     onFieldAdd: function(map, fieldId, column) {
116068         var me = this,
116069             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
116070             field = column.getEditor({ xtype: 'displayfield' });
116071
116072         me.insert(colIdx, field);
116073     },
116074
116075     onFieldRemove: function(map, fieldId, column) {
116076         var me = this,
116077             field = column.getEditor(),
116078             fieldEl = field.el;
116079         me.remove(field, false);
116080         if (fieldEl) {
116081             fieldEl.remove();
116082         }
116083     },
116084
116085     onFieldReplace: function(map, fieldId, column, oldColumn) {
116086         var me = this;
116087         me.onFieldRemove(map, fieldId, oldColumn);
116088     },
116089
116090     clearFields: function() {
116091         var me = this,
116092             map = me.columns;
116093         map.each(function(fieldId) {
116094             map.removeAtKey(fieldId);
116095         });
116096     },
116097
116098     getFloatingButtons: function() {
116099         var me = this,
116100             cssPrefix = Ext.baseCSSPrefix,
116101             btnsCss = cssPrefix + 'grid-row-editor-buttons',
116102             plugin = me.editingPlugin,
116103             btns;
116104
116105         if (!me.floatingButtons) {
116106             btns = me.floatingButtons = Ext.create('Ext.Container', {
116107                 renderTpl: [
116108                     '<div class="{baseCls}-ml"></div>',
116109                     '<div class="{baseCls}-mr"></div>',
116110                     '<div class="{baseCls}-bl"></div>',
116111                     '<div class="{baseCls}-br"></div>',
116112                     '<div class="{baseCls}-bc"></div>'
116113                 ],
116114
116115                 renderTo: me.el,
116116                 baseCls: btnsCss,
116117                 layout: {
116118                     type: 'hbox',
116119                     align: 'middle'
116120                 },
116121                 defaults: {
116122                     margins: '0 1 0 1'
116123                 },
116124                 items: [{
116125                     itemId: 'update',
116126                     flex: 1,
116127                     xtype: 'button',
116128                     handler: plugin.completeEdit,
116129                     scope: plugin,
116130                     text: me.saveBtnText,
116131                     disabled: !me.isValid
116132                 }, {
116133                     flex: 1,
116134                     xtype: 'button',
116135                     handler: plugin.cancelEdit,
116136                     scope: plugin,
116137                     text: me.cancelBtnText
116138                 }]
116139             });
116140
116141             // Prevent from bubbling click events to the grid view
116142             me.mon(btns.el, {
116143                 // BrowserBug: Opera 11.01
116144                 //   causes the view to scroll when a button is focused from mousedown
116145                 mousedown: Ext.emptyFn,
116146                 click: Ext.emptyFn,
116147                 stopEvent: true
116148             });
116149         }
116150         return me.floatingButtons;
116151     },
116152
116153     reposition: function(animateConfig) {
116154         var me = this,
116155             context = me.context,
116156             row = context && Ext.get(context.row),
116157             btns = me.getFloatingButtons(),
116158             btnEl = btns.el,
116159             grid = me.editingPlugin.grid,
116160             viewEl = grid.view.el,
116161             scroller = grid.verticalScroller,
116162
116163             // always get data from ColumnModel as its what drives
116164             // the GridView's sizing
116165             mainBodyWidth = grid.headerCt.getFullWidth(),
116166             scrollerWidth = grid.getWidth(),
116167
116168             // use the minimum as the columns may not fill up the entire grid
116169             // width
116170             width = Math.min(mainBodyWidth, scrollerWidth),
116171             scrollLeft = grid.view.el.dom.scrollLeft,
116172             btnWidth = btns.getWidth(),
116173             left = (width - btnWidth) / 2 + scrollLeft,
116174             y, rowH, newHeight,
116175
116176             invalidateScroller = function() {
116177                 if (scroller) {
116178                     scroller.invalidate();
116179                     btnEl.scrollIntoView(viewEl, false);
116180                 }
116181                 if (animateConfig && animateConfig.callback) {
116182                     animateConfig.callback.call(animateConfig.scope || me);
116183                 }
116184             };
116185
116186         // need to set both top/left
116187         if (row && Ext.isElement(row.dom)) {
116188             // Bring our row into view if necessary, so a row editor that's already
116189             // visible and animated to the row will appear smooth
116190             row.scrollIntoView(viewEl, false);
116191
116192             // Get the y position of the row relative to its top-most static parent.
116193             // offsetTop will be relative to the table, and is incorrect
116194             // when mixed with certain grid features (e.g., grouping).
116195             y = row.getXY()[1] - 5;
116196             rowH = row.getHeight();
116197             newHeight = rowH + 10;
116198
116199             // IE doesn't set the height quite right.
116200             // This isn't a border-box issue, it even happens
116201             // in IE8 and IE7 quirks.
116202             // TODO: Test in IE9!
116203             if (Ext.isIE) {
116204                 newHeight += 2;
116205             }
116206
116207             // Set editor height to match the row height
116208             if (me.getHeight() != newHeight) {
116209                 me.setHeight(newHeight);
116210                 me.el.setLeft(0);
116211             }
116212
116213             if (animateConfig) {
116214                 var animObj = {
116215                     to: {
116216                         y: y
116217                     },
116218                     duration: animateConfig.duration || 125,
116219                     listeners: {
116220                         afteranimate: function() {
116221                             invalidateScroller();
116222                             y = row.getXY()[1] - 5;
116223                             me.el.setY(y);
116224                         }
116225                     }
116226                 };
116227                 me.animate(animObj);
116228             } else {
116229                 me.el.setY(y);
116230                 invalidateScroller();
116231             }
116232         }
116233         if (me.getWidth() != mainBodyWidth) {
116234             me.setWidth(mainBodyWidth);
116235         }
116236         btnEl.setLeft(left);
116237     },
116238
116239     getEditor: function(fieldInfo) {
116240         var me = this;
116241
116242         if (Ext.isNumber(fieldInfo)) {
116243             // Query only form fields. This just future-proofs us in case we add
116244             // other components to RowEditor later on.  Don't want to mess with
116245             // indices.
116246             return me.query('>[isFormField]')[fieldInfo];
116247         } else if (fieldInfo instanceof Ext.grid.column.Column) {
116248             return fieldInfo.getEditor();
116249         }
116250     },
116251
116252     removeField: function(field) {
116253         var me = this;
116254
116255         // Incase we pass a column instead, which is fine
116256         field = me.getEditor(field);
116257         me.mun(field, 'validitychange', me.onValidityChange, me);
116258
116259         // Remove field/column from our mapping, which will fire the event to
116260         // remove the field from our container
116261         me.columns.removeKey(field.id);
116262     },
116263
116264     setField: function(column) {
116265         var me = this,
116266             field;
116267
116268         if (Ext.isArray(column)) {
116269             Ext.Array.forEach(column, me.setField, me);
116270             return;
116271         }
116272
116273         // Get a default display field if necessary
116274         field = column.getEditor(null, {
116275             xtype: 'displayfield',
116276             // Default display fields will not return values. This is done because
116277             // the display field will pick up column renderers from the grid.
116278             getModelData: function() {
116279                 return null;
116280             }
116281         });
116282         field.margins = '0 0 0 2';
116283         field.setWidth(column.getDesiredWidth() - 2);
116284         me.mon(field, 'change', me.onFieldChange, me);
116285
116286         // Maintain mapping of fields-to-columns
116287         // This will fire events that maintain our container items
116288         me.columns.add(field.id, column);
116289         if (column.hidden) {
116290             me.onColumnHide(column);
116291         }
116292         if (me.isVisible() && me.context) {
116293             me.renderColumnData(field, me.context.record);
116294         }
116295     },
116296
116297     loadRecord: function(record) {
116298         var me = this,
116299             form = me.getForm();
116300         form.loadRecord(record);
116301         if (form.isValid()) {
116302             me.hideToolTip();
116303         } else {
116304             me.showToolTip();
116305         }
116306
116307         // render display fields so they honor the column renderer/template
116308         Ext.Array.forEach(me.query('>displayfield'), function(field) {
116309             me.renderColumnData(field, record);
116310         }, me);
116311     },
116312
116313     renderColumnData: function(field, record) {
116314         var me = this,
116315             grid = me.editingPlugin.grid,
116316             headerCt = grid.headerCt,
116317             view = grid.view,
116318             store = view.store,
116319             column = me.columns.get(field.id),
116320             value = record.get(column.dataIndex);
116321
116322         // honor our column's renderer (TemplateHeader sets renderer for us!)
116323         if (column.renderer) {
116324             var metaData = { tdCls: '', style: '' },
116325                 rowIdx = store.indexOf(record),
116326                 colIdx = headerCt.getHeaderIndex(column);
116327
116328             value = column.renderer.call(
116329                 column.scope || headerCt.ownerCt,
116330                 value,
116331                 metaData,
116332                 record,
116333                 rowIdx,
116334                 colIdx,
116335                 store,
116336                 view
116337             );
116338         }
116339
116340         field.setRawValue(value);
116341         field.resetOriginalValue();
116342     },
116343
116344     beforeEdit: function() {
116345         var me = this;
116346
116347         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
116348             me.showToolTip();
116349             return false;
116350         }
116351     },
116352
116353     /**
116354      * Start editing the specified grid at the specified position.
116355      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
116356      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
116357      */
116358     startEdit: function(record, columnHeader) {
116359         var me = this,
116360             grid = me.editingPlugin.grid,
116361             view = grid.getView(),
116362             store = grid.store,
116363             context = me.context = Ext.apply(me.editingPlugin.context, {
116364                 view: grid.getView(),
116365                 store: store
116366             });
116367
116368         // make sure our row is selected before editing
116369         context.grid.getSelectionModel().select(record);
116370
116371         // Reload the record data
116372         me.loadRecord(record);
116373
116374         if (!me.isVisible()) {
116375             me.show();
116376             me.focusContextCell();
116377         } else {
116378             me.reposition({
116379                 callback: this.focusContextCell
116380             });
116381         }
116382     },
116383
116384     // Focus the cell on start edit based upon the current context
116385     focusContextCell: function() {
116386         var field = this.getEditor(this.context.colIdx);
116387         if (field && field.focus) {
116388             field.focus();
116389         }
116390     },
116391
116392     cancelEdit: function() {
116393         var me = this,
116394             form = me.getForm();
116395
116396         me.hide();
116397         form.clearInvalid();
116398         form.reset();
116399     },
116400
116401     completeEdit: function() {
116402         var me = this,
116403             form = me.getForm();
116404
116405         if (!form.isValid()) {
116406             return;
116407         }
116408
116409         form.updateRecord(me.context.record);
116410         me.hide();
116411         return true;
116412     },
116413
116414     onShow: function() {
116415         var me = this;
116416         me.callParent(arguments);
116417         me.reposition();
116418     },
116419
116420     onHide: function() {
116421         var me = this;
116422         me.callParent(arguments);
116423         me.hideToolTip();
116424         me.invalidateScroller();
116425         if (me.context) {
116426             me.context.view.focus();
116427             me.context = null;
116428         }
116429     },
116430
116431     isDirty: function() {
116432         var me = this,
116433             form = me.getForm();
116434         return form.isDirty();
116435     },
116436
116437     getToolTip: function() {
116438         var me = this,
116439             tip;
116440
116441         if (!me.tooltip) {
116442             tip = me.tooltip = Ext.createWidget('tooltip', {
116443                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
116444                 title: me.errorsText,
116445                 autoHide: false,
116446                 closable: true,
116447                 closeAction: 'disable',
116448                 anchor: 'left'
116449             });
116450         }
116451         return me.tooltip;
116452     },
116453
116454     hideToolTip: function() {
116455         var me = this,
116456             tip = me.getToolTip();
116457         if (tip.rendered) {
116458             tip.disable();
116459         }
116460         me.hiddenTip = false;
116461     },
116462
116463     showToolTip: function() {
116464         var me = this,
116465             tip = me.getToolTip(),
116466             context = me.context,
116467             row = Ext.get(context.row),
116468             viewEl = context.grid.view.el;
116469
116470         tip.setTarget(row);
116471         tip.showAt([-10000, -10000]);
116472         tip.body.update(me.getErrors());
116473         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
116474         me.repositionTip();
116475         tip.doLayout();
116476         tip.enable();
116477     },
116478
116479     repositionTip: function() {
116480         var me = this,
116481             tip = me.getToolTip(),
116482             context = me.context,
116483             row = Ext.get(context.row),
116484             viewEl = context.grid.view.el,
116485             viewHeight = viewEl.getHeight(),
116486             viewTop = me.lastScrollTop,
116487             viewBottom = viewTop + viewHeight,
116488             rowHeight = row.getHeight(),
116489             rowTop = row.dom.offsetTop,
116490             rowBottom = rowTop + rowHeight;
116491
116492         if (rowBottom > viewTop && rowTop < viewBottom) {
116493             tip.show();
116494             me.hiddenTip = false;
116495         } else {
116496             tip.hide();
116497             me.hiddenTip = true;
116498         }
116499     },
116500
116501     getErrors: function() {
116502         var me = this,
116503             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
116504             errors = [];
116505
116506         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
116507             errors = errors.concat(
116508                 Ext.Array.map(field.getErrors(), function(e) {
116509                     return '<li>' + e + '</li>';
116510                 })
116511             );
116512         }, me);
116513
116514         return dirtyText + '<ul>' + errors.join('') + '</ul>';
116515     },
116516
116517     invalidateScroller: function() {
116518         var me = this,
116519             context = me.context,
116520             scroller = context.grid.verticalScroller;
116521
116522         if (scroller) {
116523             scroller.invalidate();
116524         }
116525     }
116526 });
116527 /**
116528  * @class Ext.grid.header.Container
116529  * @extends Ext.container.Container
116530  *
116531  * Container which holds headers and is docked at the top or bottom of a TablePanel.
116532  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
116533  * As headers are hidden, moved or resized the headercontainer is responsible for
116534  * triggering changes within the view.
116535  */
116536 Ext.define('Ext.grid.header.Container', {
116537     extend: 'Ext.container.Container',
116538     uses: [
116539         'Ext.grid.ColumnLayout',
116540         'Ext.grid.column.Column',
116541         'Ext.menu.Menu',
116542         'Ext.menu.CheckItem',
116543         'Ext.menu.Separator',
116544         'Ext.grid.plugin.HeaderResizer',
116545         'Ext.grid.plugin.HeaderReorderer'
116546     ],
116547     border: true,
116548
116549     alias: 'widget.headercontainer',
116550
116551     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
116552     dock: 'top',
116553
116554     /**
116555      * @cfg {Number} weight
116556      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
116557      * This is so that it has more priority over things like toolbars.
116558      */
116559     weight: 100,
116560     defaultType: 'gridcolumn',
116561     /**
116562      * @cfg {Number} defaultWidth
116563      * Width of the header if no width or flex is specified. Defaults to 100.
116564      */
116565     defaultWidth: 100,
116566
116567
116568     sortAscText: 'Sort Ascending',
116569     sortDescText: 'Sort Descending',
116570     sortClearText: 'Clear Sort',
116571     columnsText: 'Columns',
116572
116573     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
116574     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
116575     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
116576
116577     // private; will probably be removed by 4.0
116578     triStateSort: false,
116579
116580     ddLock: false,
116581
116582     dragging: false,
116583
116584     /**
116585      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
116586      * @type Boolean
116587      * @property isGroupHeader
116588      */
116589
116590     /**
116591      * @cfg {Boolean} sortable
116592      * Provides the default sortable state for all Headers within this HeaderContainer.
116593      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
116594      * shared across every header and therefore turning it off will remove the menu
116595      * items for every header.
116596      */
116597     sortable: true,
116598
116599     initComponent: function() {
116600         var me = this;
116601
116602         me.headerCounter = 0;
116603         me.plugins = me.plugins || [];
116604
116605         // TODO: Pass in configurations to turn on/off dynamic
116606         //       resizing and disable resizing all together
116607
116608         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
116609         // Nested Group Headers are themselves HeaderContainers
116610         if (!me.isHeader) {
116611             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
116612             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
116613             if (!me.enableColumnResize) {
116614                 me.resizer.disable();
116615             }
116616             if (!me.enableColumnMove) {
116617                 me.reorderer.disable();
116618             }
116619             me.plugins.push(me.reorderer, me.resizer);
116620         }
116621
116622         // Base headers do not need a box layout
116623         if (me.isHeader && !me.items) {
116624             me.layout = 'auto';
116625         }
116626         // HeaderContainer and Group header needs a gridcolumn layout.
116627         else {
116628             me.layout = {
116629                 type: 'gridcolumn',
116630                 availableSpaceOffset: me.availableSpaceOffset,
116631                 align: 'stretchmax',
116632                 resetStretch: true
116633             };
116634         }
116635         me.defaults = me.defaults || {};
116636         Ext.applyIf(me.defaults, {
116637             width: me.defaultWidth,
116638             triStateSort: me.triStateSort,
116639             sortable: me.sortable
116640         });
116641         me.callParent();
116642         me.addEvents(
116643             /**
116644              * @event columnresize
116645              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116646              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116647              * @param {Number} width
116648              */
116649             'columnresize',
116650
116651             /**
116652              * @event headerclick
116653              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116654              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116655              * @param {Ext.EventObject} e
116656              * @param {HTMLElement} t
116657              */
116658             'headerclick',
116659
116660             /**
116661              * @event headertriggerclick
116662              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116663              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116664              * @param {Ext.EventObject} e
116665              * @param {HTMLElement} t
116666              */
116667             'headertriggerclick',
116668
116669             /**
116670              * @event columnmove
116671              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116672              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116673              * @param {Number} fromIdx
116674              * @param {Number} toIdx
116675              */
116676             'columnmove',
116677             /**
116678              * @event columnhide
116679              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116680              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116681              */
116682             'columnhide',
116683             /**
116684              * @event columnshow
116685              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116686              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116687              */
116688             'columnshow',
116689             /**
116690              * @event sortchange
116691              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
116692              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
116693              * @param {String} direction
116694              */
116695             'sortchange',
116696             /**
116697              * @event menucreate
116698              * Fired immediately after the column header menu is created.
116699              * @param {Ext.grid.header.Container} ct This instance
116700              * @param {Ext.menu.Menu} menu The Menu that was created
116701              */
116702             'menucreate'
116703         );
116704     },
116705
116706     onDestroy: function() {
116707         Ext.destroy(this.resizer, this.reorderer);
116708         this.callParent();
116709     },
116710     
116711     applyDefaults: function(config){
116712         /*
116713          * Ensure header.Container defaults don't get applied to a RowNumberer 
116714          * if an xtype is supplied. This isn't an ideal solution however it's 
116715          * much more likely that a RowNumberer with no options will be created, 
116716          * wanting to use the defaults specified on the class as opposed to 
116717          * those setup on the Container.
116718          */
116719         if (config && !config.isComponent && config.xtype == 'rownumberer') {
116720             return config;
116721         }
116722         return this.callParent([config]);
116723     },
116724
116725     applyColumnsState: function(columns) {
116726         if (!columns || !columns.length) {
116727             return;
116728         }
116729
116730         var me = this,
116731             i = 0,
116732             index,
116733             col;
116734
116735         Ext.each(columns, function (columnState) {
116736             col = me.down('gridcolumn[headerId=' + columnState.id + ']');
116737             if (col) {
116738                 index = me.items.indexOf(col);
116739                 if (i !== index) {
116740                     me.moveHeader(index, i);
116741                 }
116742
116743                 if (col.applyColumnState) {
116744                     col.applyColumnState(columnState);
116745                 }
116746                 ++i;
116747             }
116748         });
116749     },
116750
116751     getColumnsState: function () {
116752         var me = this,
116753             columns = [],
116754             state;
116755
116756         me.items.each(function (col) {
116757             state = col.getColumnState && col.getColumnState();
116758             if (state) {
116759                 columns.push(state);
116760             }
116761         });
116762
116763         return columns;
116764     },
116765
116766     // Invalidate column cache on add
116767     // We cannot refresh the View on every add because this method is called
116768     // when the HeaderDropZone moves Headers around, that will also refresh the view
116769     onAdd: function(c) {
116770         var me = this;
116771         if (!c.headerId) {
116772             c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
116773         }
116774         if (Ext.global.console && Ext.global.console.warn) {
116775             if (!me._usedIDs) me._usedIDs = {};
116776             if (me._usedIDs[c.headerId]) {
116777                 Ext.global.console.warn(this.$className, 'attempted to reuse an existing id', c.headerId);
116778             }
116779             me._usedIDs[c.headerId] = true;
116780         }
116781         me.callParent(arguments);
116782         me.purgeCache();
116783     },
116784
116785     // Invalidate column cache on remove
116786     // We cannot refresh the View on every remove because this method is called
116787     // when the HeaderDropZone moves Headers around, that will also refresh the view
116788     onRemove: function(c) {
116789         var me = this;
116790         me.callParent(arguments);
116791         me.purgeCache();
116792     },
116793
116794     afterRender: function() {
116795         this.callParent();
116796         var store   = this.up('[store]').store,
116797             sorters = store.sorters,
116798             first   = sorters.first(),
116799             hd;
116800
116801         if (first) {
116802             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
116803             if (hd) {
116804                 hd.setSortState(first.direction, false, true);
116805             }
116806         }
116807     },
116808
116809     afterLayout: function() {
116810         if (!this.isHeader) {
116811             var me = this,
116812                 topHeaders = me.query('>gridcolumn:not([hidden])'),
116813                 viewEl,
116814                 firstHeaderEl,
116815                 lastHeaderEl;
116816
116817             me.callParent(arguments);
116818
116819             if (topHeaders.length) {
116820                 firstHeaderEl = topHeaders[0].el;
116821                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
116822                     if (me.pastFirstHeaderEl) {
116823                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
116824                     }
116825                     firstHeaderEl.addCls(me.firstHeaderCls);
116826                     me.pastFirstHeaderEl = firstHeaderEl;
116827                 }
116828
116829                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
116830                 if (lastHeaderEl !== me.pastLastHeaderEl) {
116831                     if (me.pastLastHeaderEl) {
116832                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
116833                     }
116834                     lastHeaderEl.addCls(me.lastHeaderCls);
116835                     me.pastLastHeaderEl = lastHeaderEl;
116836                 }
116837             }
116838         }
116839
116840     },
116841
116842     onHeaderShow: function(header, preventLayout) {
116843         // Pass up to the GridSection
116844         var me = this,
116845             gridSection = me.ownerCt,
116846             menu = me.getMenu(),
116847             topItems, topItemsVisible,
116848             colCheckItem,
116849             itemToEnable,
116850             len, i;
116851
116852         if (menu) {
116853
116854             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116855             if (colCheckItem) {
116856                 colCheckItem.setChecked(true, true);
116857             }
116858
116859             // There's more than one header visible, and we've disabled some checked items... re-enable them
116860             topItems = menu.query('#columnItem>menucheckitem[checked]');
116861             topItemsVisible = topItems.length;
116862             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
116863                 if (topItemsVisible == 1) {
116864                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
116865                 }
116866                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
116867                     itemToEnable = me.disabledMenuItems[i];
116868                     if (!itemToEnable.isDestroyed) {
116869                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
116870                     }
116871                 }
116872                 if (topItemsVisible == 1) {
116873                     me.disabledMenuItems = topItems;
116874                 } else {
116875                     me.disabledMenuItems = [];
116876                 }
116877             }
116878         }
116879
116880         // Only update the grid UI when we are notified about base level Header shows;
116881         // Group header shows just cause a layout of the HeaderContainer
116882         if (!header.isGroupHeader) {
116883             if (me.view) {
116884                 me.view.onHeaderShow(me, header, true);
116885             }
116886             if (gridSection) {
116887                 gridSection.onHeaderShow(me, header);
116888             }
116889         }
116890         me.fireEvent('columnshow', me, header);
116891
116892         // The header's own hide suppresses cascading layouts, so lay the headers out now
116893         if (preventLayout !== true) {
116894             me.doLayout();
116895         }
116896     },
116897
116898     doComponentLayout: function(){
116899         var me = this;
116900         if (me.view && me.view.saveScrollState) {
116901             me.view.saveScrollState();
116902         }
116903         me.callParent(arguments);
116904         if (me.view && me.view.restoreScrollState) {
116905             me.view.restoreScrollState();
116906         }
116907     },
116908
116909     onHeaderHide: function(header, suppressLayout) {
116910         // Pass up to the GridSection
116911         var me = this,
116912             gridSection = me.ownerCt,
116913             menu = me.getMenu(),
116914             colCheckItem;
116915
116916         if (menu) {
116917
116918             // If the header was hidden programmatically, sync the Menu state
116919             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116920             if (colCheckItem) {
116921                 colCheckItem.setChecked(false, true);
116922             }
116923             me.setDisabledItems();
116924         }
116925
116926         // Only update the UI when we are notified about base level Header hides;
116927         if (!header.isGroupHeader) {
116928             if (me.view) {
116929                 me.view.onHeaderHide(me, header, true);
116930             }
116931             if (gridSection) {
116932                 gridSection.onHeaderHide(me, header);
116933             }
116934
116935             // The header's own hide suppresses cascading layouts, so lay the headers out now
116936             if (!suppressLayout) {
116937                 me.doLayout();
116938             }
116939         }
116940         me.fireEvent('columnhide', me, header);
116941     },
116942
116943     setDisabledItems: function(){
116944         var me = this,
116945             menu = me.getMenu(),
116946             i = 0,
116947             len,
116948             itemsToDisable,
116949             itemToDisable;
116950
116951         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
116952         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
116953         if ((itemsToDisable.length === 1)) {
116954             if (!me.disabledMenuItems) {
116955                 me.disabledMenuItems = [];
116956             }
116957
116958             // If down to only one column visible, also disable any descendant checkitems
116959             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
116960                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
116961             }
116962
116963             len = itemsToDisable.length;
116964             // Disable any further unchecking at any level.
116965             for (i = 0; i < len; i++) {
116966                 itemToDisable = itemsToDisable[i];
116967                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
116968
116969                     // If we only want to disable check change: it might be a disabled item, so enable it prior to
116970                     // setting its correct disablement level.
116971                     itemToDisable.disabled = false;
116972                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
116973                     me.disabledMenuItems.push(itemToDisable);
116974                 }
116975             }
116976         }
116977     },
116978
116979     /**
116980      * Temporarily lock the headerCt. This makes it so that clicking on headers
116981      * don't trigger actions like sorting or opening of the header menu. This is
116982      * done because extraneous events may be fired on the headers after interacting
116983      * with a drag drop operation.
116984      * @private
116985      */
116986     tempLock: function() {
116987         this.ddLock = true;
116988         Ext.Function.defer(function() {
116989             this.ddLock = false;
116990         }, 200, this);
116991     },
116992
116993     onHeaderResize: function(header, w, suppressFocus) {
116994         this.tempLock();
116995         if (this.view && this.view.rendered) {
116996             this.view.onHeaderResize(header, w, suppressFocus);
116997         }
116998     },
116999
117000     onHeaderClick: function(header, e, t) {
117001         this.fireEvent("headerclick", this, header, e, t);
117002     },
117003
117004     onHeaderTriggerClick: function(header, e, t) {
117005         // generate and cache menu, provide ability to cancel/etc
117006         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
117007             this.showMenuBy(t, header);
117008         }
117009     },
117010
117011     showMenuBy: function(t, header) {
117012         var menu = this.getMenu(),
117013             ascItem  = menu.down('#ascItem'),
117014             descItem = menu.down('#descItem'),
117015             sortableMth;
117016
117017         menu.activeHeader = menu.ownerCt = header;
117018         menu.setFloatParent(header);
117019         // TODO: remove coupling to Header's titleContainer el
117020         header.titleContainer.addCls(this.headerOpenCls);
117021
117022         // enable or disable asc & desc menu items based on header being sortable
117023         sortableMth = header.sortable ? 'enable' : 'disable';
117024         if (ascItem) {
117025             ascItem[sortableMth]();
117026         }
117027         if (descItem) {
117028             descItem[sortableMth]();
117029         }
117030         menu.showBy(t);
117031     },
117032
117033     // remove the trigger open class when the menu is hidden
117034     onMenuDeactivate: function() {
117035         var menu = this.getMenu();
117036         // TODO: remove coupling to Header's titleContainer el
117037         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
117038     },
117039
117040     moveHeader: function(fromIdx, toIdx) {
117041
117042         // An automatically expiring lock
117043         this.tempLock();
117044         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
117045     },
117046
117047     purgeCache: function() {
117048         var me = this;
117049         // Delete column cache - column order has changed.
117050         delete me.gridDataColumns;
117051         delete me.hideableColumns;
117052
117053         // Menu changes when columns are moved. It will be recreated.
117054         if (me.menu) {
117055             me.menu.destroy();
117056             delete me.menu;
117057         }
117058     },
117059
117060     onHeaderMoved: function(header, fromIdx, toIdx) {
117061         var me = this,
117062             gridSection = me.ownerCt;
117063
117064         if (gridSection && gridSection.onHeaderMove) {
117065             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
117066         }
117067         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
117068     },
117069
117070     /**
117071      * Gets the menu (and will create it if it doesn't already exist)
117072      * @private
117073      */
117074     getMenu: function() {
117075         var me = this;
117076
117077         if (!me.menu) {
117078             me.menu = Ext.create('Ext.menu.Menu', {
117079                 hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
117080                 items: me.getMenuItems(),
117081                 listeners: {
117082                     deactivate: me.onMenuDeactivate,
117083                     scope: me
117084                 }
117085             });
117086             me.setDisabledItems();
117087             me.fireEvent('menucreate', me, me.menu);
117088         }
117089         return me.menu;
117090     },
117091
117092     /**
117093      * Returns an array of menu items to be placed into the shared menu
117094      * across all headers in this header container.
117095      * @returns {Array} menuItems
117096      */
117097     getMenuItems: function() {
117098         var me = this,
117099             menuItems = [],
117100             hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
117101
117102         if (me.sortable) {
117103             menuItems = [{
117104                 itemId: 'ascItem',
117105                 text: me.sortAscText,
117106                 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
117107                 handler: me.onSortAscClick,
117108                 scope: me
117109             },{
117110                 itemId: 'descItem',
117111                 text: me.sortDescText,
117112                 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
117113                 handler: me.onSortDescClick,
117114                 scope: me
117115             }];
117116         }
117117         if (hideableColumns && hideableColumns.length) {
117118             menuItems.push('-', {
117119                 itemId: 'columnItem',
117120                 text: me.columnsText,
117121                 cls: Ext.baseCSSPrefix + 'cols-icon',
117122                 menu: hideableColumns
117123             });
117124         }
117125         return menuItems;
117126     },
117127
117128     // sort asc when clicking on item in menu
117129     onSortAscClick: function() {
117130         var menu = this.getMenu(),
117131             activeHeader = menu.activeHeader;
117132
117133         activeHeader.setSortState('ASC');
117134     },
117135
117136     // sort desc when clicking on item in menu
117137     onSortDescClick: function() {
117138         var menu = this.getMenu(),
117139             activeHeader = menu.activeHeader;
117140
117141         activeHeader.setSortState('DESC');
117142     },
117143
117144     /**
117145      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
117146      */
117147     getColumnMenu: function(headerContainer) {
117148         var menuItems = [],
117149             i = 0,
117150             item,
117151             items = headerContainer.query('>gridcolumn[hideable]'),
117152             itemsLn = items.length,
117153             menuItem;
117154
117155         for (; i < itemsLn; i++) {
117156             item = items[i];
117157             menuItem = Ext.create('Ext.menu.CheckItem', {
117158                 text: item.text,
117159                 checked: !item.hidden,
117160                 hideOnClick: false,
117161                 headerId: item.id,
117162                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
117163                 checkHandler: this.onColumnCheckChange,
117164                 scope: this
117165             });
117166             if (itemsLn === 1) {
117167                 menuItem.disabled = true;
117168             }
117169             menuItems.push(menuItem);
117170
117171             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
117172             // then the associated menu item must also be destroyed.
117173             item.on({
117174                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
117175             });
117176         }
117177         return menuItems;
117178     },
117179
117180     onColumnCheckChange: function(checkItem, checked) {
117181         var header = Ext.getCmp(checkItem.headerId);
117182         header[checked ? 'show' : 'hide']();
117183     },
117184
117185     /**
117186      * Get the columns used for generating a template via TableChunker.
117187      * Returns an array of all columns and their
117188      *  - dataIndex
117189      *  - align
117190      *  - width
117191      *  - id
117192      *  - columnId - used to create an identifying CSS class
117193      *  - cls The tdCls configuration from the Column object
117194      *  @private
117195      */
117196     getColumnsForTpl: function(flushCache) {
117197         var cols    = [],
117198             headers   = this.getGridColumns(flushCache),
117199             headersLn = headers.length,
117200             i = 0,
117201             header,
117202             width;
117203
117204         for (; i < headersLn; i++) {
117205             header = headers[i];
117206
117207             if (header.hidden || header.up('headercontainer[hidden=true]')) {
117208                 width = 0;
117209             } else {
117210                 width = header.getDesiredWidth();
117211                 // IE6 and IE7 bug.
117212                 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
117213                 // We need to increment the passed with in this case.
117214                 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
117215                     width += 1;
117216                 }
117217             }
117218             cols.push({
117219                 dataIndex: header.dataIndex,
117220                 align: header.align,
117221                 width: width,
117222                 id: header.id,
117223                 cls: header.tdCls,
117224                 columnId: header.getItemId()
117225             });
117226         }
117227         return cols;
117228     },
117229
117230     /**
117231      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
117232      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
117233      */
117234     getColumnCount: function() {
117235         return this.getGridColumns().length;
117236     },
117237
117238     /**
117239      * Gets the full width of all columns that are visible.
117240      */
117241     getFullWidth: function(flushCache) {
117242         var fullWidth = 0,
117243             headers     = this.getVisibleGridColumns(flushCache),
117244             headersLn   = headers.length,
117245             i         = 0;
117246
117247         for (; i < headersLn; i++) {
117248             if (!isNaN(headers[i].width)) {
117249                 // use headers getDesiredWidth if its there
117250                 if (headers[i].getDesiredWidth) {
117251                     fullWidth += headers[i].getDesiredWidth();
117252                 // if injected a diff cmp use getWidth
117253                 } else {
117254                     fullWidth += headers[i].getWidth();
117255                 }
117256             }
117257         }
117258         return fullWidth;
117259     },
117260
117261     // invoked internally by a header when not using triStateSorting
117262     clearOtherSortStates: function(activeHeader) {
117263         var headers   = this.getGridColumns(),
117264             headersLn = headers.length,
117265             i         = 0,
117266             oldSortState;
117267
117268         for (; i < headersLn; i++) {
117269             if (headers[i] !== activeHeader) {
117270                 oldSortState = headers[i].sortState;
117271                 // unset the sortstate and dont recurse
117272                 headers[i].setSortState(null, true);
117273                 //if (!silent && oldSortState !== null) {
117274                 //    this.fireEvent('sortchange', this, headers[i], null);
117275                 //}
117276             }
117277         }
117278     },
117279
117280     /**
117281      * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
117282      * level, and does not return <i>grouped</i> headers which contain sub headers.
117283      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
117284      * @returns {Array}
117285      */
117286     getVisibleGridColumns: function(refreshCache) {
117287         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
117288     },
117289
117290     /**
117291      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
117292      * level, and does not return <i>grouped</i> headers which contain sub headers.
117293      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
117294      * @returns {Array}
117295      */
117296     getGridColumns: function(refreshCache) {
117297         var me = this,
117298             result = refreshCache ? null : me.gridDataColumns;
117299
117300         // Not already got the column cache, so collect the base columns
117301         if (!result) {
117302             me.gridDataColumns = result = [];
117303             me.cascade(function(c) {
117304                 if ((c !== me) && !c.isGroupHeader) {
117305                     result.push(c);
117306                 }
117307             });
117308         }
117309
117310         return result;
117311     },
117312
117313     /**
117314      * @private
117315      * For use by column headers in determining whether there are any hideable columns when deciding whether or not
117316      * the header menu should be disabled.
117317      */
117318     getHideableColumns: function(refreshCache) {
117319         var me = this,
117320             result = refreshCache ? null : me.hideableColumns;
117321
117322         if (!result) {
117323             result = me.hideableColumns = me.query('[hideable]');
117324         }
117325         return result;
117326     },
117327
117328     /**
117329      * Get the index of a leaf level header regardless of what the nesting
117330      * structure is.
117331      */
117332     getHeaderIndex: function(header) {
117333         var columns = this.getGridColumns();
117334         return Ext.Array.indexOf(columns, header);
117335     },
117336
117337     /**
117338      * Get a leaf level header by index regardless of what the nesting
117339      * structure is.
117340      */
117341     getHeaderAtIndex: function(index) {
117342         var columns = this.getGridColumns();
117343         return columns[index];
117344     },
117345
117346     /**
117347      * Maps the record data to base it on the header id's.
117348      * This correlates to the markup/template generated by
117349      * TableChunker.
117350      */
117351     prepareData: function(data, rowIdx, record, view, panel) {
117352         var obj       = {},
117353             headers   = this.gridDataColumns || this.getGridColumns(),
117354             headersLn = headers.length,
117355             colIdx    = 0,
117356             header,
117357             headerId,
117358             renderer,
117359             value,
117360             metaData,
117361             store = panel.store;
117362
117363         for (; colIdx < headersLn; colIdx++) {
117364             metaData = {
117365                 tdCls: '',
117366                 style: ''
117367             };
117368             header = headers[colIdx];
117369             headerId = header.id;
117370             renderer = header.renderer;
117371             value = data[header.dataIndex];
117372
117373             // When specifying a renderer as a string, it always resolves
117374             // to Ext.util.Format
117375             if (typeof renderer === "string") {
117376                 header.renderer = renderer = Ext.util.Format[renderer];
117377             }
117378
117379             if (typeof renderer === "function") {
117380                 value = renderer.call(
117381                     header.scope || this.ownerCt,
117382                     value,
117383                     // metadata per cell passing an obj by reference so that
117384                     // it can be manipulated inside the renderer
117385                     metaData,
117386                     record,
117387                     rowIdx,
117388                     colIdx,
117389                     store,
117390                     view
117391                 );
117392             }
117393
117394             if (metaData.css) {
117395                 // This warning attribute is used by the compat layer
117396                 obj.cssWarning = true;
117397                 metaData.tdCls = metaData.css;
117398                 delete metaData.css;
117399             }
117400
117401             obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
117402             obj[headerId+'-tdCls'] = metaData.tdCls;
117403             obj[headerId+'-tdAttr'] = metaData.tdAttr;
117404             obj[headerId+'-style'] = metaData.style;
117405             if (value === undefined || value === null || value === '') {
117406                 value = '&#160;';
117407             }
117408             obj[headerId] = value;
117409         }
117410         return obj;
117411     },
117412
117413     expandToFit: function(header) {
117414         if (this.view) {
117415             this.view.expandToFit(header);
117416         }
117417     }
117418 });
117419
117420 /**
117421  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
117422  * both the grid header configuration as well as displaying data within the grid itself. If the
117423  * {@link #columns} configuration is specified, this column will become a column group and can
117424  * contain other columns inside. In general, this class will not be created directly, rather
117425  * an array of column configurations will be passed to the grid:
117426  *
117427  *     @example
117428  *     Ext.create('Ext.data.Store', {
117429  *         storeId:'employeeStore',
117430  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
117431  *         data:[
117432  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
117433  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
117434  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
117435  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
117436  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
117437  *         ]
117438  *     });
117439  *
117440  *     Ext.create('Ext.grid.Panel', {
117441  *         title: 'Column Demo',
117442  *         store: Ext.data.StoreManager.lookup('employeeStore'),
117443  *         columns: [
117444  *             {text: 'First Name',  dataIndex:'firstname'},
117445  *             {text: 'Last Name',  dataIndex:'lastname'},
117446  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
117447  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
117448  *         ],
117449  *         width: 400,
117450  *         renderTo: Ext.getBody()
117451  *     });
117452  *
117453  * # Convenience Subclasses
117454  *
117455  * There are several column subclasses that provide default rendering for various data types
117456  *
117457  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
117458  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
117459  *  - {@link Ext.grid.column.Date}: Renders for date values
117460  *  - {@link Ext.grid.column.Number}: Renders for numeric values
117461  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
117462  *
117463  * # Setting Sizes
117464  *
117465  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
117466  * be given an explicit width value or a flex configuration. If no width is specified the grid will
117467  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
117468  * the width of the child columns, so a width option should not be specified in that case.
117469  *
117470  * # Header Options
117471  *
117472  *  - {@link #text}: Sets the header text for the column
117473  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
117474  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
117475  *  - {@link #menuDisabled}: Disables the column header menu
117476  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
117477  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
117478  *
117479  * # Data Options
117480  *
117481  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
117482  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
117483  */
117484 Ext.define('Ext.grid.column.Column', {
117485     extend: 'Ext.grid.header.Container',
117486     alias: 'widget.gridcolumn',
117487     requires: ['Ext.util.KeyNav'],
117488     alternateClassName: 'Ext.grid.Column',
117489
117490     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
117491
117492     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
117493     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
117494
117495     handleWidth: 5,
117496
117497     sortState: null,
117498
117499     possibleSortStates: ['ASC', 'DESC'],
117500
117501     renderTpl:
117502         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
117503             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
117504                 '{text}' +
117505             '</span>' +
117506             '<tpl if="!values.menuDisabled">'+
117507                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
117508             '</tpl>' +
117509         '</div>',
117510
117511     /**
117512      * @cfg {Object[]} columns
117513      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
117514      * `columns` config.
117515      *
117516      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
117517      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
117518      */
117519
117520     /**
117521      * @cfg {String} dataIndex
117522      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
117523      * which to draw the column's value. **Required.**
117524      */
117525     dataIndex: null,
117526
117527     /**
117528      * @cfg {String} text
117529      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
117530      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
117531      */
117532     text: '&#160;',
117533
117534     /**
117535      * @cfg {Boolean} sortable
117536      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
117537      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
117538      */
117539     sortable: true,
117540
117541     /**
117542      * @cfg {Boolean} groupable
117543      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
117544      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
117545      * disable (but still show) the group option in the header menu for the column.
117546      */
117547
117548     /**
117549      * @cfg {Boolean} fixed
117550      * @deprecated.
117551      * True to prevent the column from being resizable.
117552      */
117553
117554     /**
117555      * @cfg {Boolean} resizable
117556      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
117557      */
117558
117559     /**
117560      * @cfg {Boolean} hideable
117561      * False to prevent the user from hiding this column. Defaults to true.
117562      */
117563     hideable: true,
117564
117565     /**
117566      * @cfg {Boolean} menuDisabled
117567      * True to disable the column header menu containing sort/hide options. Defaults to false.
117568      */
117569     menuDisabled: false,
117570
117571     /**
117572      * @cfg {Function} renderer
117573      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
117574      * before it is rendered. Example:
117575      *
117576      *     {
117577      *         renderer: function(value){
117578      *             if (value === 1) {
117579      *                 return '1 person';
117580      *             }
117581      *             return value + ' people';
117582      *         }
117583      *     }
117584      *
117585      * @cfg {Object} renderer.value The data value for the current cell
117586      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
117587      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
117588      * @cfg {Ext.data.Model} renderer.record The record for the current row
117589      * @cfg {Number} renderer.rowIndex The index of the current row
117590      * @cfg {Number} renderer.colIndex The index of the current column
117591      * @cfg {Ext.data.Store} renderer.store The data store
117592      * @cfg {Ext.view.View} renderer.view The current view
117593      * @cfg {String} renderer.return The HTML string to be rendered.
117594      */
117595     renderer: false,
117596
117597     /**
117598      * @cfg {String} align
117599      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
117600      */
117601     align: 'left',
117602
117603     /**
117604      * @cfg {Boolean} draggable
117605      * False to disable drag-drop reordering of this column. Defaults to true.
117606      */
117607     draggable: true,
117608
117609     // Header does not use the typical ComponentDraggable class and therefore we
117610     // override this with an emptyFn. It is controlled at the HeaderDragZone.
117611     initDraggable: Ext.emptyFn,
117612
117613     /**
117614      * @cfg {String} tdCls
117615      * A CSS class names to apply to the table cells for this column.
117616      */
117617
117618     /**
117619      * @cfg {Object/String} editor
117620      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
117621      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
117622      */
117623
117624     /**
117625      * @cfg {Object/String} field
117626      * Alias for {@link #editor}.
117627      * @deprecated 4.0.5 Use {@link #editor} instead.
117628      */
117629
117630     /**
117631      * @property {Ext.Element} triggerEl
117632      * Element that acts as button for column header dropdown menu.
117633      */
117634
117635     /**
117636      * @property {Ext.Element} textEl
117637      * Element that contains the text in column header.
117638      */
117639
117640     /**
117641      * @private
117642      * Set in this class to identify, at runtime, instances which are not instances of the
117643      * HeaderContainer base class, but are in fact, the subclass: Header.
117644      */
117645     isHeader: true,
117646
117647     initComponent: function() {
117648         var me = this,
117649             i,
117650             len,
117651             item;
117652
117653         if (Ext.isDefined(me.header)) {
117654             me.text = me.header;
117655             delete me.header;
117656         }
117657
117658         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
117659         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
117660         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
117661         if (me.flex) {
117662             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
117663         }
117664         // Non-flexed Headers may never be squeezed in the event of a shortfall so
117665         // always set their minWidth to their current width.
117666         else {
117667             me.minWidth = me.width;
117668         }
117669
117670         if (!me.triStateSort) {
117671             me.possibleSortStates.length = 2;
117672         }
117673
117674         // A group header; It contains items which are themselves Headers
117675         if (Ext.isDefined(me.columns)) {
117676             me.isGroupHeader = true;
117677
117678             if (me.dataIndex) {
117679                 Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
117680             }
117681             if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
117682                 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.');
117683             }
117684
117685             // The headers become child items
117686             me.items = me.columns;
117687             delete me.columns;
117688             delete me.flex;
117689             me.width = 0;
117690
117691             // Acquire initial width from sub headers
117692             for (i = 0, len = me.items.length; i < len; i++) {
117693                 item = me.items[i];
117694                 if (!item.hidden) {
117695                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
117696                 }
117697                 if (item.flex) {
117698                     Ext.Error.raise('Ext.grid.column.Column: items of a grouped header do not support flexed values. Each item must explicitly define its width.');
117699                 }
117700             }
117701             me.minWidth = me.width;
117702
117703             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
117704             me.sortable = false;
117705             me.resizable = false;
117706             me.align = 'center';
117707         }
117708
117709         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
117710
117711         // Initialize as a HeaderContainer
117712         me.callParent(arguments);
117713     },
117714
117715     onAdd: function(childHeader) {
117716         childHeader.isSubHeader = true;
117717         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
117718         this.callParent(arguments);
117719     },
117720
117721     onRemove: function(childHeader) {
117722         childHeader.isSubHeader = false;
117723         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
117724         this.callParent(arguments);
117725     },
117726
117727     initRenderData: function() {
117728         var me = this;
117729
117730         Ext.applyIf(me.renderData, {
117731             text: me.text,
117732             menuDisabled: me.menuDisabled
117733         });
117734         return me.callParent(arguments);
117735     },
117736
117737     applyColumnState: function (state) {
117738         var me = this,
117739             defined = Ext.isDefined;
117740             
117741         // apply any columns
117742         me.applyColumnsState(state.columns);
117743
117744         // Only state properties which were saved should be restored.
117745         // (Only user-changed properties were saved by getState)
117746         if (defined(state.hidden)) {
117747             me.hidden = state.hidden;
117748         }
117749         if (defined(state.locked)) {
117750             me.locked = state.locked;
117751         }
117752         if (defined(state.sortable)) {
117753             me.sortable = state.sortable;
117754         }
117755         if (defined(state.width)) {
117756             delete me.flex;
117757             me.width = state.width;
117758         } else if (defined(state.flex)) {
117759             delete me.width;
117760             me.flex = state.flex;
117761         }
117762     },
117763
117764     getColumnState: function () {
117765         var me = this,
117766             columns = [],
117767             state = {
117768                 id: me.headerId
117769             };
117770
117771         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
117772         
117773         if (me.isGroupHeader) {
117774             me.items.each(function(column){
117775                 columns.push(column.getColumnState());
117776             });
117777             if (columns.length) {
117778                 state.columns = columns;
117779             }
117780         } else if (me.isSubHeader && me.ownerCt.hidden) {
117781             // don't set hidden on the children so they can auto height
117782             delete me.hidden;
117783         }
117784
117785         if ('width' in state) {
117786             delete state.flex; // width wins
117787         }
117788         return state;
117789     },
117790
117791     /**
117792      * Sets the header text for this Column.
117793      * @param {String} text The header to display on this Column.
117794      */
117795     setText: function(text) {
117796         this.text = text;
117797         if (this.rendered) {
117798             this.textEl.update(text);
117799         }
117800     },
117801
117802     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
117803     // Group Headers are themselves HeaderContainers
117804     getOwnerHeaderCt: function() {
117805         return this.up(':not([isHeader])');
117806     },
117807
117808     /**
117809      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
117810      * is a group column, it returns `false`.
117811      * @return {Number}
117812      */
117813     getIndex: function() {
117814         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
117815     },
117816
117817     onRender: function() {
117818         var me = this,
117819             grid = me.up('tablepanel');
117820
117821         // Disable the menu if there's nothing to show in the menu, ie:
117822         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
117823         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
117824             me.menuDisabled = true;
117825         }
117826         me.callParent(arguments);
117827     },
117828
117829     afterRender: function() {
117830         var me = this,
117831             el = me.el;
117832
117833         me.callParent(arguments);
117834
117835         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
117836
117837         me.mon(el, {
117838             click:     me.onElClick,
117839             dblclick:  me.onElDblClick,
117840             scope:     me
117841         });
117842
117843         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
117844         // must be fixed when focus management will be implemented.
117845         if (!Ext.isIE8 || !Ext.isStrict) {
117846             me.mon(me.getFocusEl(), {
117847                 focus: me.onTitleMouseOver,
117848                 blur: me.onTitleMouseOut,
117849                 scope: me
117850             });
117851         }
117852
117853         me.mon(me.titleContainer, {
117854             mouseenter:  me.onTitleMouseOver,
117855             mouseleave:  me.onTitleMouseOut,
117856             scope:      me
117857         });
117858
117859         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
117860             enter: me.onEnterKey,
117861             down: me.onDownKey,
117862             scope: me
117863         });
117864     },
117865
117866     /**
117867      * Sets the width of this Column.
117868      * @param {Number} width New width.
117869      */
117870     setWidth: function(width, /* private - used internally */ doLayout) {
117871         var me = this,
117872             headerCt = me.ownerCt,
117873             siblings,
117874             len, i,
117875             oldWidth = me.getWidth(),
117876             groupWidth = 0,
117877             sibling;
117878
117879         if (width !== oldWidth) {
117880             me.oldWidth = oldWidth;
117881
117882             // Non-flexed Headers may never be squeezed in the event of a shortfall so
117883             // always set the minWidth to their current width.
117884             me.minWidth = me.width = width;
117885
117886             // Bubble size changes upwards to group headers
117887             if (headerCt.isGroupHeader) {
117888                 siblings = headerCt.items.items;
117889                 len = siblings.length;
117890
117891                 for (i = 0; i < len; i++) {
117892                     sibling = siblings[i];
117893                     if (!sibling.hidden) {
117894                         groupWidth += (sibling === me) ? width : sibling.getWidth();
117895                     }
117896                 }
117897                 headerCt.setWidth(groupWidth, doLayout);
117898             } else if (doLayout !== false) {
117899                 // Allow the owning Container to perform the sizing
117900                 headerCt.doLayout();
117901             }
117902         }
117903     },
117904
117905     afterComponentLayout: function(width, height) {
117906         var me = this,
117907             ownerHeaderCt = this.getOwnerHeaderCt();
117908
117909         me.callParent(arguments);
117910
117911         // Only changes at the base level inform the grid's HeaderContainer which will update the View
117912         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
117913         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
117914         if (width && !me.isGroupHeader && ownerHeaderCt) {
117915             ownerHeaderCt.onHeaderResize(me, width, true);
117916         }
117917         if (me.oldWidth && (width !== me.oldWidth)) {
117918             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
117919         }
117920         delete me.oldWidth;
117921     },
117922
117923     // private
117924     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
117925     // Total available header height must be passed to enable padding for inner elements to be calculated.
117926     setPadding: function(headerHeight) {
117927         var me = this,
117928             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
117929
117930         // Top title containing element must stretch to match height of sibling group headers
117931         if (!me.isGroupHeader) {
117932             if (me.titleContainer.getHeight() < headerHeight) {
117933                 me.titleContainer.dom.style.height = headerHeight + 'px';
117934             }
117935         }
117936         headerHeight = me.titleContainer.getViewSize().height;
117937
117938         // Vertically center the header text in potentially vertically stretched header
117939         if (lineHeight) {
117940             me.titleContainer.setStyle({
117941                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
117942             });
117943         }
117944
117945         // Only IE needs this
117946         if (Ext.isIE && me.triggerEl) {
117947             me.triggerEl.setHeight(headerHeight);
117948         }
117949     },
117950
117951     onDestroy: function() {
117952         var me = this;
117953         // force destroy on the textEl, IE reports a leak
117954         Ext.destroy(me.textEl, me.keyNav);
117955         delete me.keyNav;
117956         me.callParent(arguments);
117957     },
117958
117959     onTitleMouseOver: function() {
117960         this.titleContainer.addCls(this.hoverCls);
117961     },
117962
117963     onTitleMouseOut: function() {
117964         this.titleContainer.removeCls(this.hoverCls);
117965     },
117966
117967     onDownKey: function(e) {
117968         if (this.triggerEl) {
117969             this.onElClick(e, this.triggerEl.dom || this.el.dom);
117970         }
117971     },
117972
117973     onEnterKey: function(e) {
117974         this.onElClick(e, this.el.dom);
117975     },
117976
117977     /**
117978      * @private
117979      * Double click
117980      * @param e
117981      * @param t
117982      */
117983     onElDblClick: function(e, t) {
117984         var me = this,
117985             ownerCt = me.ownerCt;
117986         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
117987             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
117988         }
117989     },
117990
117991     onElClick: function(e, t) {
117992
117993         // The grid's docked HeaderContainer.
117994         var me = this,
117995             ownerHeaderCt = me.getOwnerHeaderCt();
117996
117997         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
117998             // Firefox doesn't check the current target in a within check.
117999             // Therefore we check the target directly and then within (ancestors)
118000             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
118001                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
118002             // if its not on the left hand edge, sort
118003             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
118004                 me.toggleSortState();
118005                 ownerHeaderCt.onHeaderClick(me, e, t);
118006             }
118007         }
118008     },
118009
118010     /**
118011      * @private
118012      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
118013      * @param {String} type Event type, eg 'click'
118014      * @param {Ext.view.Table} view TableView Component
118015      * @param {HTMLElement} cell Cell HtmlElement the event took place within
118016      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
118017      * @param {Number} cellIndex Cell index within the row
118018      * @param {Ext.EventObject} e Original event
118019      */
118020     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
118021         return this.fireEvent.apply(this, arguments);
118022     },
118023
118024     toggleSortState: function() {
118025         var me = this,
118026             idx,
118027             nextIdx;
118028
118029         if (me.sortable) {
118030             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
118031
118032             nextIdx = (idx + 1) % me.possibleSortStates.length;
118033             me.setSortState(me.possibleSortStates[nextIdx]);
118034         }
118035     },
118036
118037     doSort: function(state) {
118038         var ds = this.up('tablepanel').store;
118039         ds.sort({
118040             property: this.getSortParam(),
118041             direction: state
118042         });
118043     },
118044
118045     /**
118046      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
118047      * need to be overriden in most cases.
118048      * @return {String}
118049      */
118050     getSortParam: function() {
118051         return this.dataIndex;
118052     },
118053
118054     //setSortState: function(state, updateUI) {
118055     //setSortState: function(state, doSort) {
118056     setSortState: function(state, skipClear, initial) {
118057         var me = this,
118058             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
118059             ascCls = colSortClsPrefix + 'ASC',
118060             descCls = colSortClsPrefix + 'DESC',
118061             nullCls = colSortClsPrefix + 'null',
118062             ownerHeaderCt = me.getOwnerHeaderCt(),
118063             oldSortState = me.sortState;
118064
118065         if (oldSortState !== state && me.getSortParam()) {
118066             me.addCls(colSortClsPrefix + state);
118067             // don't trigger a sort on the first time, we just want to update the UI
118068             if (state && !initial) {
118069                 me.doSort(state);
118070             }
118071             switch (state) {
118072                 case 'DESC':
118073                     me.removeCls([ascCls, nullCls]);
118074                     break;
118075                 case 'ASC':
118076                     me.removeCls([descCls, nullCls]);
118077                     break;
118078                 case null:
118079                     me.removeCls([ascCls, descCls]);
118080                     break;
118081             }
118082             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
118083                 ownerHeaderCt.clearOtherSortStates(me);
118084             }
118085             me.sortState = state;
118086             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
118087         }
118088     },
118089
118090     hide: function() {
118091         var me = this,
118092             items,
118093             len, i,
118094             lb,
118095             newWidth = 0,
118096             ownerHeaderCt = me.getOwnerHeaderCt();
118097
118098         // Hiding means setting to zero width, so cache the width
118099         me.oldWidth = me.getWidth();
118100
118101         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
118102         if (me.isGroupHeader) {
118103             items = me.items.items;
118104             me.callParent(arguments);
118105             ownerHeaderCt.onHeaderHide(me);
118106             for (i = 0, len = items.length; i < len; i++) {
118107                 items[i].hidden = true;
118108                 ownerHeaderCt.onHeaderHide(items[i], true);
118109             }
118110             return;
118111         }
118112
118113         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
118114         lb = me.ownerCt.componentLayout.layoutBusy;
118115         me.ownerCt.componentLayout.layoutBusy = true;
118116         me.callParent(arguments);
118117         me.ownerCt.componentLayout.layoutBusy = lb;
118118
118119         // Notify owning HeaderContainer
118120         ownerHeaderCt.onHeaderHide(me);
118121
118122         if (me.ownerCt.isGroupHeader) {
118123             // If we've just hidden the last header in a group, then hide the group
118124             items = me.ownerCt.query('>:not([hidden])');
118125             if (!items.length) {
118126                 me.ownerCt.hide();
118127             }
118128             // Size the group down to accommodate fewer sub headers
118129             else {
118130                 for (i = 0, len = items.length; i < len; i++) {
118131                     newWidth += items[i].getWidth();
118132                 }
118133                 me.ownerCt.minWidth = newWidth;
118134                 me.ownerCt.setWidth(newWidth);
118135             }
118136         }
118137     },
118138
118139     show: function() {
118140         var me = this,
118141             ownerCt = me.ownerCt,
118142             ownerCtCompLayout = ownerCt.componentLayout,
118143             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
118144             ownerCtLayout = ownerCt.layout,
118145             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
118146             items,
118147             len, i,
118148             item,
118149             newWidth = 0;
118150
118151         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
118152
118153         // Suspend our owner's layouts (both component and container):
118154         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
118155
118156         me.callParent(arguments);
118157
118158         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
118159         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
118160
118161         // If a sub header, ensure that the group header is visible
118162         if (me.isSubHeader) {
118163             if (!ownerCt.isVisible()) {
118164                 ownerCt.show();
118165             }
118166         }
118167
118168         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
118169         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
118170             items = me.query('>*');
118171             for (i = 0, len = items.length; i < len; i++) {
118172                 item = items[i];
118173                 item.preventLayout = true;
118174                 item.show();
118175                 newWidth += item.getWidth();
118176                 delete item.preventLayout;
118177             }
118178             me.setWidth(newWidth);
118179         }
118180
118181         // Resize the owning group to accommodate
118182         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
118183             items = ownerCt.query('>:not([hidden])');
118184             for (i = 0, len = items.length; i < len; i++) {
118185                 newWidth += items[i].getWidth();
118186             }
118187             ownerCt.minWidth = newWidth;
118188             ownerCt.setWidth(newWidth);
118189         }
118190
118191         // Notify owning HeaderContainer
118192         ownerCt = me.getOwnerHeaderCt();
118193         if (ownerCt) {
118194             ownerCt.onHeaderShow(me, me.preventLayout);
118195         }
118196     },
118197
118198     getDesiredWidth: function() {
118199         var me = this;
118200         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
118201             // headers always have either a width or a flex
118202             // because HeaderContainer sets a defaults width
118203             // therefore we can ignore the natural width
118204             // we use the componentLayout's tracked width so that
118205             // we can calculate the desired width when rendered
118206             // but not visible because its being obscured by a layout
118207             return me.componentLayout.lastComponentSize.width;
118208         // Flexed but yet to be rendered this could be the case
118209         // where a HeaderContainer and Headers are simply used as data
118210         // structures and not rendered.
118211         }
118212         else if (me.flex) {
118213             // this is going to be wrong, the defaultWidth
118214             return me.width;
118215         }
118216         else {
118217             return me.width;
118218         }
118219     },
118220
118221     getCellSelector: function() {
118222         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
118223     },
118224
118225     getCellInnerSelector: function() {
118226         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
118227     },
118228
118229     isOnLeftEdge: function(e) {
118230         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
118231     },
118232
118233     isOnRightEdge: function(e) {
118234         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
118235     }
118236
118237     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
118238     // when the editing plugin is injected
118239
118240     /**
118241      * @method getEditor
118242      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
118243      * associated with the Header the method will return false. If the field has not been instantiated it will be
118244      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
118245      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
118246      * @param {Object} defaultField An object representing a default field to be created
118247      * @return {Ext.form.field.Field} field
118248      */
118249     /**
118250      * @method setEditor
118251      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
118252      * been enabled on the grid.
118253      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
118254      * assumed.
118255      */
118256 });
118257
118258 /**
118259  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
118260  * an automatic row numbering column.
118261  * 
118262  * Usage:
118263  *
118264  *     columns: [
118265  *         {xtype: 'rownumberer'},
118266  *         {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
118267  *         {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
118268  *         {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
118269  *         {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
118270  *         {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
118271  *     ]
118272  *
118273  */
118274 Ext.define('Ext.grid.RowNumberer', {
118275     extend: 'Ext.grid.column.Column',
118276     alias: 'widget.rownumberer',
118277
118278     /**
118279      * @cfg {String} text
118280      * Any valid text or HTML fragment to display in the header cell for the row number column.
118281      */
118282     text: "&#160",
118283
118284     /**
118285      * @cfg {Number} width
118286      * The default width in pixels of the row number column.
118287      */
118288     width: 23,
118289
118290     /**
118291      * @cfg {Boolean} sortable
118292      * True if the row number column is sortable.
118293      * @hide
118294      */
118295     sortable: false,
118296
118297     align: 'right',
118298
118299     constructor : function(config){
118300         this.callParent(arguments);
118301         if (this.rowspan) {
118302             this.renderer = Ext.Function.bind(this.renderer, this);
118303         }
118304     },
118305
118306     // private
118307     resizable: false,
118308     hideable: false,
118309     menuDisabled: true,
118310     dataIndex: '',
118311     cls: Ext.baseCSSPrefix + 'row-numberer',
118312     rowspan: undefined,
118313
118314     // private
118315     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
118316         if (this.rowspan){
118317             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
118318         }
118319
118320         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
118321         return store.indexOfTotal(record) + 1;
118322     }
118323 });
118324
118325 /**
118326  * @class Ext.view.DropZone
118327  * @extends Ext.dd.DropZone
118328  * @private
118329  */
118330 Ext.define('Ext.view.DropZone', {
118331     extend: 'Ext.dd.DropZone',
118332
118333     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
118334     indicatorCls: 'x-grid-drop-indicator',
118335
118336     constructor: function(config) {
118337         var me = this;
118338         Ext.apply(me, config);
118339
118340         // Create a ddGroup unless one has been configured.
118341         // User configuration of ddGroups allows users to specify which
118342         // DD instances can interact with each other. Using one
118343         // based on the id of the View would isolate it and mean it can only
118344         // interact with a DragZone on the same View also using a generated ID.
118345         if (!me.ddGroup) {
118346             me.ddGroup = 'view-dd-zone-' + me.view.id;
118347         }
118348
118349         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
118350         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
118351         // same element, so a DragZone on this same View must use the View's parent element as its element.
118352         me.callParent([me.view.el]);
118353     },
118354
118355 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
118356 //  its data does not become corrupted by processing mouse events.
118357     fireViewEvent: function() {
118358         var me = this,
118359             result;
118360
118361         me.lock();
118362         result = me.view.fireEvent.apply(me.view, arguments);
118363         me.unlock();
118364         return result;
118365     },
118366
118367     getTargetFromEvent : function(e) {
118368         var node = e.getTarget(this.view.getItemSelector()),
118369             mouseY, nodeList, testNode, i, len, box;
118370
118371 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
118372 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
118373         if (!node) {
118374             mouseY = e.getPageY();
118375             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
118376                 testNode = nodeList[i];
118377                 box = Ext.fly(testNode).getBox();
118378                 if (mouseY <= box.bottom) {
118379                     return testNode;
118380                 }
118381             }
118382         }
118383         return node;
118384     },
118385
118386     getIndicator: function() {
118387         var me = this;
118388
118389         if (!me.indicator) {
118390             me.indicator = Ext.createWidget('component', {
118391                 html: me.indicatorHtml,
118392                 cls: me.indicatorCls,
118393                 ownerCt: me.view,
118394                 floating: true,
118395                 shadow: false
118396             });
118397         }
118398         return me.indicator;
118399     },
118400
118401     getPosition: function(e, node) {
118402         var y      = e.getXY()[1],
118403             region = Ext.fly(node).getRegion(),
118404             pos;
118405
118406         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
118407             pos = "before";
118408         } else {
118409             pos = "after";
118410         }
118411         return pos;
118412     },
118413
118414     /**
118415      * @private Determines whether the record at the specified offset from the passed record
118416      * is in the drag payload.
118417      * @param records
118418      * @param record
118419      * @param offset
118420      * @returns {Boolean} True if the targeted record is in the drag payload
118421      */
118422     containsRecordAtOffset: function(records, record, offset) {
118423         if (!record) {
118424             return false;
118425         }
118426         var view = this.view,
118427             recordIndex = view.indexOf(record),
118428             nodeBefore = view.getNode(recordIndex + offset),
118429             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
118430
118431         return recordBefore && Ext.Array.contains(records, recordBefore);
118432     },
118433
118434     positionIndicator: function(node, data, e) {
118435         var me = this,
118436             view = me.view,
118437             pos = me.getPosition(e, node),
118438             overRecord = view.getRecord(node),
118439             draggingRecords = data.records,
118440             indicator, indicatorY;
118441
118442         if (!Ext.Array.contains(draggingRecords, overRecord) && (
118443             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
118444             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
118445         )) {
118446             me.valid = true;
118447
118448             if (me.overRecord != overRecord || me.currentPosition != pos) {
118449
118450                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
118451                 if (pos == 'after') {
118452                     indicatorY += Ext.fly(node).getHeight();
118453                 }
118454                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
118455
118456                 // Cache the overRecord and the 'before' or 'after' indicator.
118457                 me.overRecord = overRecord;
118458                 me.currentPosition = pos;
118459             }
118460         } else {
118461             me.invalidateDrop();
118462         }
118463     },
118464
118465     invalidateDrop: function() {
118466         if (this.valid) {
118467             this.valid = false;
118468             this.getIndicator().hide();
118469         }
118470     },
118471
118472     // The mouse is over a View node
118473     onNodeOver: function(node, dragZone, e, data) {
118474         var me = this;
118475
118476         if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
118477             me.positionIndicator(node, data, e);
118478         }
118479         return me.valid ? me.dropAllowed : me.dropNotAllowed;
118480     },
118481
118482     // Moved out of the DropZone without dropping.
118483     // Remove drop position indicator
118484     notifyOut: function(node, dragZone, e, data) {
118485         var me = this;
118486
118487         me.callParent(arguments);
118488         delete me.overRecord;
118489         delete me.currentPosition;
118490         if (me.indicator) {
118491             me.indicator.hide();
118492         }
118493     },
118494
118495     // The mouse is past the end of all nodes (or there are no nodes)
118496     onContainerOver : function(dd, e, data) {
118497         var me = this,
118498             view = me.view,
118499             count = view.store.getCount();
118500
118501         // There are records, so position after the last one
118502         if (count) {
118503             me.positionIndicator(view.getNode(count - 1), data, e);
118504         }
118505
118506         // No records, position the indicator at the top
118507         else {
118508             delete me.overRecord;
118509             delete me.currentPosition;
118510             me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
118511             me.valid = true;
118512         }
118513         return me.dropAllowed;
118514     },
118515
118516     onContainerDrop : function(dd, e, data) {
118517         return this.onNodeDrop(dd, null, e, data);
118518     },
118519
118520     onNodeDrop: function(node, dragZone, e, data) {
118521         var me = this,
118522             dropped = false,
118523
118524             // Create a closure to perform the operation which the event handler may use.
118525             // Users may now return <code>false</code> from the beforedrop handler, and perform any kind
118526             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
118527             // and complete the drop gesture at some point in the future by calling this function.
118528             processDrop = function () {
118529                 me.invalidateDrop();
118530                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
118531                 dropped = true;
118532                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
118533             },
118534             performOperation = false;
118535
118536         if (me.valid) {
118537             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
118538             if (performOperation !== false) {
118539                 // If the processDrop function was called in the event handler, do not do it again.
118540                 if (!dropped) {
118541                     processDrop();
118542                 }
118543             }
118544         }
118545         return performOperation;
118546     },
118547     
118548     destroy: function(){
118549         Ext.destroy(this.indicator);
118550         delete this.indicator;
118551         this.callParent();
118552     }
118553 });
118554
118555 Ext.define('Ext.grid.ViewDropZone', {
118556     extend: 'Ext.view.DropZone',
118557
118558     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
118559     indicatorCls: 'x-grid-drop-indicator',
118560
118561     handleNodeDrop : function(data, record, position) {
118562         var view = this.view,
118563             store = view.getStore(),
118564             index, records, i, len;
118565
118566         // If the copy flag is set, create a copy of the Models with the same IDs
118567         if (data.copy) {
118568             records = data.records;
118569             data.records = [];
118570             for (i = 0, len = records.length; i < len; i++) {
118571                 data.records.push(records[i].copy(records[i].getId()));
118572             }
118573         } else {
118574             /*
118575              * Remove from the source store. We do this regardless of whether the store
118576              * is the same bacsue the store currently doesn't handle moving records
118577              * within the store. In the future it should be possible to do this.
118578              * Here was pass the isMove parameter if we're moving to the same view.
118579              */
118580             data.view.store.remove(data.records, data.view === view);
118581         }
118582
118583         index = store.indexOf(record);
118584
118585         // 'after', or undefined (meaning a drop at index -1 on an empty View)...
118586         if (position !== 'before') {
118587             index++;
118588         }
118589         store.insert(index, data.records);
118590         view.getSelectionModel().select(data.records);
118591     }
118592 });
118593 /**
118594  * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
118595  * handler for each icon.
118596  *
118597  *     @example
118598  *     Ext.create('Ext.data.Store', {
118599  *         storeId:'employeeStore',
118600  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
118601  *         data:[
118602  *             {firstname:"Michael", lastname:"Scott"},
118603  *             {firstname:"Dwight", lastname:"Schrute"},
118604  *             {firstname:"Jim", lastname:"Halpert"},
118605  *             {firstname:"Kevin", lastname:"Malone"},
118606  *             {firstname:"Angela", lastname:"Martin"}
118607  *         ]
118608  *     });
118609  *
118610  *     Ext.create('Ext.grid.Panel', {
118611  *         title: 'Action Column Demo',
118612  *         store: Ext.data.StoreManager.lookup('employeeStore'),
118613  *         columns: [
118614  *             {text: 'First Name',  dataIndex:'firstname'},
118615  *             {text: 'Last Name',  dataIndex:'lastname'},
118616  *             {
118617  *                 xtype:'actioncolumn',
118618  *                 width:50,
118619  *                 items: [{
118620  *                     icon: 'extjs/examples/shared/icons/fam/cog_edit.png',  // Use a URL in the icon config
118621  *                     tooltip: 'Edit',
118622  *                     handler: function(grid, rowIndex, colIndex) {
118623  *                         var rec = grid.getStore().getAt(rowIndex);
118624  *                         alert("Edit " + rec.get('firstname'));
118625  *                     }
118626  *                 },{
118627  *                     icon: 'extjs/examples/restful/images/delete.png',
118628  *                     tooltip: 'Delete',
118629  *                     handler: function(grid, rowIndex, colIndex) {
118630  *                         var rec = grid.getStore().getAt(rowIndex);
118631  *                         alert("Terminate " + rec.get('firstname'));
118632  *                     }
118633  *                 }]
118634  *             }
118635  *         ],
118636  *         width: 250,
118637  *         renderTo: Ext.getBody()
118638  *     });
118639  *
118640  * The action column can be at any index in the columns array, and a grid can have any number of
118641  * action columns.
118642  */
118643 Ext.define('Ext.grid.column.Action', {
118644     extend: 'Ext.grid.column.Column',
118645     alias: ['widget.actioncolumn'],
118646     alternateClassName: 'Ext.grid.ActionColumn',
118647
118648     /**
118649      * @cfg {String} icon
118650      * The URL of an image to display as the clickable element in the column. Defaults to
118651      * `{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}`.
118652      */
118653     /**
118654      * @cfg {String} iconCls
118655      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
118656      * a `{@link #getClass}` function.
118657      */
118658     /**
118659      * @cfg {Function} handler
118660      * A function called when the icon is clicked.
118661      * @cfg {Ext.view.Table} handler.view The owning TableView.
118662      * @cfg {Number} handler.rowIndex The row index clicked on.
118663      * @cfg {Number} handler.colIndex The column index clicked on.
118664      * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #items} were not configured).
118665      * @cfg {Event} handler.e The click event.
118666      */
118667     /**
118668      * @cfg {Object} scope
118669      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
118670      * Defaults to this Column.
118671      */
118672     /**
118673      * @cfg {String} tooltip
118674      * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
118675      * have been initialized.
118676      */
118677     /* @cfg {Boolean} disabled
118678      * If true, the action will not respond to click events, and will be displayed semi-opaque.
118679      */
118680     /**
118681      * @cfg {Boolean} [stopSelection=true]
118682      * Prevent grid _row_ selection upon mousedown.
118683      */
118684     /**
118685      * @cfg {Function} getClass
118686      * A function which returns the CSS class to apply to the icon image.
118687      *
118688      * @cfg {Object} getClass.v The value of the column's configured field (if any).
118689      *
118690      * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
118691      * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
118692      * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
118693      * element *within* the table cell (e.g. 'style="color:red;"').
118694      *
118695      * @cfg {Ext.data.Model} getClass.r The Record providing the data.
118696      *
118697      * @cfg {Number} getClass.rowIndex The row index..
118698      *
118699      * @cfg {Number} getClass.colIndex The column index.
118700      *
118701      * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
118702      */
118703     /**
118704      * @cfg {Object[]} items
118705      * An Array which may contain multiple icon definitions, each element of which may contain:
118706      *
118707      * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
118708      *
118709      * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
118710      * configure the item with a `getClass` function.
118711      *
118712      * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
118713      * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
118714      * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
118715      * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
118716      * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
118717      * container element _within_ the table cell (e.g. 'style="color:red;"').
118718      * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
118719      * @cfg {Number} items.getClass.rowIndex The row index..
118720      * @cfg {Number} items.getClass.colIndex The column index.
118721      * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
118722      *
118723      * @cfg {Function} items.handler A function called when the icon is clicked.
118724      *
118725      * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
118726      * are executed. Fallback defaults are this Column's configured scope, then this Column.
118727      *
118728      * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
118729      * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
118730      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
118731      */
118732     /**
118733      * @property {Array} items
118734      * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
118735      * an `enable` and `disable` method added which will enable and disable the associated action, and
118736      * update the displayed icon accordingly.
118737      */
118738     header: '&#160;',
118739
118740     actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
118741
118742     /**
118743      * @cfg {String} altText
118744      * The alt text to use for the image element.
118745      */
118746     altText: '',
118747
118748     sortable: false,
118749
118750     constructor: function(config) {
118751         var me = this,
118752             cfg = Ext.apply({}, config),
118753             items = cfg.items || [me],
118754             l = items.length,
118755             i,
118756             item;
118757
118758         // This is a Container. Delete the items config to be reinstated after construction.
118759         delete cfg.items;
118760         me.callParent([cfg]);
118761
118762         // Items is an array property of ActionColumns
118763         me.items = items;
118764
118765 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
118766 //      class name x-action-col-{n}
118767         me.renderer = function(v, meta) {
118768 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
118769             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
118770
118771             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
118772             for (i = 0; i < l; i++) {
118773                 item = items[i];
118774                 item.disable = Ext.Function.bind(me.disableAction, me, [i]);
118775                 item.enable = Ext.Function.bind(me.enableAction, me, [i]);
118776                 v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
118777                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
118778                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
118779                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
118780             }
118781             return v;
118782         };
118783     },
118784
118785     /**
118786      * Enables this ActionColumn's action at the specified index.
118787      */
118788     enableAction: function(index) {
118789         var me = this;
118790
118791         if (!index) {
118792             index = 0;
118793         } else if (!Ext.isNumber(index)) {
118794             index = Ext.Array.indexOf(me.items, index);
118795         }
118796         me.items[index].disabled = false;
118797         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
118798     },
118799
118800     /**
118801      * Disables this ActionColumn's action at the specified index.
118802      */
118803     disableAction: function(index) {
118804         var me = this;
118805
118806         if (!index) {
118807             index = 0;
118808         } else if (!Ext.isNumber(index)) {
118809             index = Ext.Array.indexOf(me.items, index);
118810         }
118811         me.items[index].disabled = true;
118812         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
118813     },
118814
118815     destroy: function() {
118816         delete this.items;
118817         delete this.renderer;
118818         return this.callParent(arguments);
118819     },
118820
118821     /**
118822      * @private
118823      * Process and refire events routed from the GridView's processEvent method.
118824      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
118825      * Returns the event handler's status to allow canceling of GridView's bubbling process.
118826      */
118827     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
118828         var me = this,
118829             match = e.getTarget().className.match(me.actionIdRe),
118830             item, fn;
118831             
118832         if (match) {
118833             item = me.items[parseInt(match[1], 10)];
118834             if (item) {
118835                 if (type == 'click') {
118836                     fn = item.handler || me.handler;
118837                     if (fn && !item.disabled) {
118838                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
118839                     }
118840                 } else if (type == 'mousedown' && item.stopSelection !== false) {
118841                     return false;
118842                 }
118843             }
118844         }
118845         return me.callParent(arguments);
118846     },
118847
118848     cascade: function(fn, scope) {
118849         fn.call(scope||this, this);
118850     },
118851
118852     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
118853     getRefItems: function() {
118854         return [];
118855     }
118856 });
118857 /**
118858  * A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
118859  * config option of {@link Ext.grid.column.Column} for more details.
118860  *
118861  *     @example
118862  *     Ext.create('Ext.data.Store', {
118863  *        storeId:'sampleStore',
118864  *        fields:[
118865  *            {name: 'framework', type: 'string'},
118866  *            {name: 'rocks', type: 'boolean'}
118867  *        ],
118868  *        data:{'items':[
118869  *            { 'framework': "Ext JS 4",     'rocks': true  },
118870  *            { 'framework': "Sencha Touch", 'rocks': true  },
118871  *            { 'framework': "Ext GWT",      'rocks': true  }, 
118872  *            { 'framework': "Other Guys",   'rocks': false } 
118873  *        ]},
118874  *        proxy: {
118875  *            type: 'memory',
118876  *            reader: {
118877  *                type: 'json',
118878  *                root: 'items'
118879  *            }
118880  *        }
118881  *     });
118882  *     
118883  *     Ext.create('Ext.grid.Panel', {
118884  *         title: 'Boolean Column Demo',
118885  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118886  *         columns: [
118887  *             { text: 'Framework',  dataIndex: 'framework', flex: 1 },
118888  *             {
118889  *                 xtype: 'booleancolumn', 
118890  *                 text: 'Rocks',
118891  *                 trueText: 'Yes',
118892  *                 falseText: 'No', 
118893  *                 dataIndex: 'rocks'
118894  *             }
118895  *         ],
118896  *         height: 200,
118897  *         width: 400,
118898  *         renderTo: Ext.getBody()
118899  *     });
118900  */
118901 Ext.define('Ext.grid.column.Boolean', {
118902     extend: 'Ext.grid.column.Column',
118903     alias: ['widget.booleancolumn'],
118904     alternateClassName: 'Ext.grid.BooleanColumn',
118905
118906     /**
118907      * @cfg {String} trueText
118908      * The string returned by the renderer when the column value is not falsey.
118909      */
118910     trueText: 'true',
118911
118912     /**
118913      * @cfg {String} falseText
118914      * The string returned by the renderer when the column value is falsey (but not undefined).
118915      */
118916     falseText: 'false',
118917
118918     /**
118919      * @cfg {String} undefinedText
118920      * The string returned by the renderer when the column value is undefined.
118921      */
118922     undefinedText: '&#160;',
118923
118924     constructor: function(cfg){
118925         this.callParent(arguments);
118926         var trueText      = this.trueText,
118927             falseText     = this.falseText,
118928             undefinedText = this.undefinedText;
118929
118930         this.renderer = function(value){
118931             if(value === undefined){
118932                 return undefinedText;
118933             }
118934             if(!value || value === 'false'){
118935                 return falseText;
118936             }
118937             return trueText;
118938         };
118939     }
118940 });
118941 /**
118942  * A Column definition class which renders a passed date according to the default locale, or a configured
118943  * {@link #format}.
118944  *
118945  *     @example
118946  *     Ext.create('Ext.data.Store', {
118947  *         storeId:'sampleStore',
118948  *         fields:[
118949  *             { name: 'symbol', type: 'string' },
118950  *             { name: 'date',   type: 'date' },
118951  *             { name: 'change', type: 'number' },
118952  *             { name: 'volume', type: 'number' },
118953  *             { name: 'topday', type: 'date' }                        
118954  *         ],
118955  *         data:[
118956  *             { symbol: "msft",   date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
118957  *             { symbol: "goog",   date: '2011/04/22', change: 0.81, volume: 3053782,  topday: '04/11/2010' },
118958  *             { symbol: "apple",  date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },            
118959  *             { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351,  topday: '04/22/2010' }            
118960  *         ]
118961  *     });
118962  *     
118963  *     Ext.create('Ext.grid.Panel', {
118964  *         title: 'Date Column Demo',
118965  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118966  *         columns: [
118967  *             { text: 'Symbol',   dataIndex: 'symbol', flex: 1 },
118968  *             { text: 'Date',     dataIndex: 'date',   xtype: 'datecolumn',   format:'Y-m-d' },
118969  *             { text: 'Change',   dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118970  *             { text: 'Volume',   dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
118971  *             { text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn',   format:'l' }            
118972  *         ],
118973  *         height: 200,
118974  *         width: 450,
118975  *         renderTo: Ext.getBody()
118976  *     });
118977  */
118978 Ext.define('Ext.grid.column.Date', {
118979     extend: 'Ext.grid.column.Column',
118980     alias: ['widget.datecolumn'],
118981     requires: ['Ext.Date'],
118982     alternateClassName: 'Ext.grid.DateColumn',
118983
118984     /**
118985      * @cfg {String} format
118986      * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
118987      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
118988      * in a locale file.
118989      */
118990
118991     initComponent: function(){
118992         var me = this;
118993         
118994         me.callParent(arguments);
118995         if (!me.format) {
118996             me.format = Ext.Date.defaultFormat;
118997         }
118998         me.renderer = Ext.util.Format.dateRenderer(me.format);
118999     }
119000 });
119001 /**
119002  * A Column definition class which renders a numeric data field according to a {@link #format} string.
119003  *
119004  *     @example
119005  *     Ext.create('Ext.data.Store', {
119006  *        storeId:'sampleStore',
119007  *        fields:[
119008  *            { name: 'symbol', type: 'string' },
119009  *            { name: 'price',  type: 'number' },
119010  *            { name: 'change', type: 'number' },
119011  *            { name: 'volume', type: 'number' },            
119012  *        ],
119013  *        data:[
119014  *            { symbol: "msft",   price: 25.76,  change: 2.43, volume: 61606325 },
119015  *            { symbol: "goog",   price: 525.73, change: 0.81, volume: 3053782  },
119016  *            { symbol: "apple",  price: 342.41, change: 1.35, volume: 24484858 },            
119017  *            { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351  }            
119018  *        ]
119019  *     });
119020  *     
119021  *     Ext.create('Ext.grid.Panel', {
119022  *         title: 'Number Column Demo',
119023  *         store: Ext.data.StoreManager.lookup('sampleStore'),
119024  *         columns: [
119025  *             { text: 'Symbol',         dataIndex: 'symbol', flex: 1 },
119026  *             { text: 'Current Price',  dataIndex: 'price',  renderer: Ext.util.Format.usMoney },
119027  *             { text: 'Change',         dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
119028  *             { text: 'Volume',         dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
119029  *         ],
119030  *         height: 200,
119031  *         width: 400,
119032  *         renderTo: Ext.getBody()
119033  *     });
119034  */
119035 Ext.define('Ext.grid.column.Number', {
119036     extend: 'Ext.grid.column.Column',
119037     alias: ['widget.numbercolumn'],
119038     requires: ['Ext.util.Format'],
119039     alternateClassName: 'Ext.grid.NumberColumn',
119040
119041     /**
119042      * @cfg {String} format
119043      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
119044      */
119045     format : '0,000.00',
119046
119047     constructor: function(cfg) {
119048         this.callParent(arguments);
119049         this.renderer = Ext.util.Format.numberRenderer(this.format);
119050     }
119051 });
119052 /**
119053  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
119054  * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
119055  * {@link Ext.XTemplate XTemplate}.
119056  * 
119057  *     @example
119058  *     Ext.create('Ext.data.Store', {
119059  *         storeId:'employeeStore',
119060  *         fields:['firstname', 'lastname', 'senority', 'department'],
119061  *         groupField: 'department',
119062  *         data:[
119063  *             { firstname: "Michael", lastname: "Scott",   senority: 7, department: "Manangement" },
119064  *             { firstname: "Dwight",  lastname: "Schrute", senority: 2, department: "Sales" },
119065  *             { firstname: "Jim",     lastname: "Halpert", senority: 3, department: "Sales" },
119066  *             { firstname: "Kevin",   lastname: "Malone",  senority: 4, department: "Accounting" },
119067  *             { firstname: "Angela",  lastname: "Martin",  senority: 5, department: "Accounting" }                        
119068  *         ]
119069  *     });
119070  *     
119071  *     Ext.create('Ext.grid.Panel', {
119072  *         title: 'Column Template Demo',
119073  *         store: Ext.data.StoreManager.lookup('employeeStore'),
119074  *         columns: [
119075  *             { text: 'Full Name',       xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
119076  *             { text: 'Deparment (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({senority})' }
119077  *         ],
119078  *         height: 200,
119079  *         width: 300,
119080  *         renderTo: Ext.getBody()
119081  *     });
119082  */
119083 Ext.define('Ext.grid.column.Template', {
119084     extend: 'Ext.grid.column.Column',
119085     alias: ['widget.templatecolumn'],
119086     requires: ['Ext.XTemplate'],
119087     alternateClassName: 'Ext.grid.TemplateColumn',
119088
119089     /**
119090      * @cfg {String/Ext.XTemplate} tpl
119091      * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
119092      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
119093      * column's rendered value.
119094      */
119095
119096     constructor: function(cfg){
119097         var me = this,
119098             tpl;
119099             
119100         me.callParent(arguments);
119101         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
119102
119103         me.renderer = function(value, p, record) {
119104             var data = Ext.apply({}, record.data, record.getAssociatedData());
119105             return tpl.apply(data);
119106         };
119107     }
119108 });
119109
119110 /**
119111  * @class Ext.grid.feature.Feature
119112  * @extends Ext.util.Observable
119113  * 
119114  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
119115  * hooks that allows the developer to inject additional functionality at certain points throughout the 
119116  * grid creation cycle. This class provides the base template methods that are available to the developer,
119117  * it should be extended.
119118  * 
119119  * There are several built in features that extend this class, for example:
119120  *
119121  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
119122  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
119123  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
119124  * 
119125  * ## Using Features
119126  * A feature is added to the grid by specifying it an array of features in the configuration:
119127  * 
119128  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
119129  *     Ext.create('Ext.grid.Panel', {
119130  *         // other options
119131  *         features: [groupingFeature]
119132  *     });
119133  * 
119134  * @abstract
119135  */
119136 Ext.define('Ext.grid.feature.Feature', {
119137     extend: 'Ext.util.Observable',
119138     alias: 'feature.feature',
119139     
119140     isFeature: true,
119141     disabled: false,
119142     
119143     /**
119144      * @property {Boolean}
119145      * Most features will expose additional events, some may not and will
119146      * need to change this to false.
119147      */
119148     hasFeatureEvent: true,
119149     
119150     /**
119151      * @property {String}
119152      * Prefix to use when firing events on the view.
119153      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
119154      */
119155     eventPrefix: null,
119156     
119157     /**
119158      * @property {String}
119159      * Selector used to determine when to fire the event with the eventPrefix.
119160      */
119161     eventSelector: null,
119162     
119163     /**
119164      * @property {Ext.view.Table}
119165      * Reference to the TableView.
119166      */
119167     view: null,
119168     
119169     /**
119170      * @property {Ext.grid.Panel}
119171      * Reference to the grid panel
119172      */
119173     grid: null,
119174     
119175     /**
119176      * Most features will not modify the data returned to the view.
119177      * This is limited to one feature that manipulates the data per grid view.
119178      */
119179     collectData: false,
119180         
119181     getFeatureTpl: function() {
119182         return '';
119183     },
119184     
119185     /**
119186      * Abstract method to be overriden when a feature should add additional
119187      * arguments to its event signature. By default the event will fire:
119188      * - view - The underlying Ext.view.Table
119189      * - featureTarget - The matched element by the defined {@link #eventSelector}
119190      *
119191      * The method must also return the eventName as the first index of the array
119192      * to be passed to fireEvent.
119193      * @template
119194      */
119195     getFireEventArgs: function(eventName, view, featureTarget, e) {
119196         return [eventName, view, featureTarget, e];
119197     },
119198     
119199     /**
119200      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
119201      * @template
119202      */
119203     attachEvents: function() {
119204         
119205     },
119206     
119207     getFragmentTpl: function() {
119208         return;
119209     },
119210     
119211     /**
119212      * Allows a feature to mutate the metaRowTpl.
119213      * The array received as a single argument can be manipulated to add things
119214      * on the end/begining of a particular row.
119215      * @template
119216      */
119217     mutateMetaRowTpl: function(metaRowTplArray) {
119218         
119219     },
119220     
119221     /**
119222      * Allows a feature to inject member methods into the metaRowTpl. This is
119223      * important for embedding functionality which will become part of the proper
119224      * row tpl.
119225      * @template
119226      */
119227     getMetaRowTplFragments: function() {
119228         return {};
119229     },
119230
119231     getTableFragments: function() {
119232         return {};
119233     },
119234     
119235     /**
119236      * Provide additional data to the prepareData call within the grid view.
119237      * @param {Object} data The data for this particular record.
119238      * @param {Number} idx The row index for this record.
119239      * @param {Ext.data.Model} record The record instance
119240      * @param {Object} orig The original result from the prepareData call to massage.
119241      * @template
119242      */
119243     getAdditionalData: function(data, idx, record, orig) {
119244         return {};
119245     },
119246     
119247     /**
119248      * Enable a feature
119249      */
119250     enable: function() {
119251         this.disabled = false;
119252     },
119253     
119254     /**
119255      * Disable a feature
119256      */
119257     disable: function() {
119258         this.disabled = true;
119259     }
119260     
119261 });
119262 /**
119263  * @class Ext.grid.feature.AbstractSummary
119264  * @extends Ext.grid.feature.Feature
119265  * A small abstract class that contains the shared behaviour for any summary
119266  * calculations to be used in the grid.
119267  */
119268 Ext.define('Ext.grid.feature.AbstractSummary', {
119269     
119270     /* Begin Definitions */
119271    
119272     extend: 'Ext.grid.feature.Feature',
119273     
119274     alias: 'feature.abstractsummary',
119275    
119276     /* End Definitions */
119277    
119278    /**
119279     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
119280     */
119281     showSummaryRow: true,
119282     
119283     // @private
119284     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
119285     
119286     /**
119287      * Toggle whether or not to show the summary row.
119288      * @param {Boolean} visible True to show the summary row
119289      */
119290     toggleSummaryRow: function(visible){
119291         this.showSummaryRow = !!visible;
119292     },
119293     
119294     /**
119295      * Gets any fragments to be used in the tpl
119296      * @private
119297      * @return {Object} The fragments
119298      */
119299     getSummaryFragments: function(){
119300         var fragments = {};
119301         if (this.showSummaryRow) {
119302             Ext.apply(fragments, {
119303                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
119304             });
119305         }
119306         return fragments;
119307     },
119308     
119309     /**
119310      * Prints a summary row
119311      * @private
119312      * @param {Object} index The index in the template
119313      * @return {String} The value of the summary row
119314      */
119315     printSummaryRow: function(index){
119316         var inner = this.view.getTableChunker().metaRowTpl.join(''),
119317             prefix = Ext.baseCSSPrefix;
119318         
119319         inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
119320         inner = inner.replace('{{id}}', '{gridSummaryValue}');
119321         inner = inner.replace(this.nestedIdRe, '{id$1}');  
119322         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
119323         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
119324         inner = Ext.create('Ext.XTemplate', inner, {
119325             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
119326         });
119327         
119328         return inner.applyTemplate({
119329             columns: this.getPrintData(index)
119330         });
119331     },
119332     
119333     /**
119334      * Gets the value for the column from the attached data.
119335      * @param {Ext.grid.column.Column} column The header
119336      * @param {Object} data The current data
119337      * @return {String} The value to be rendered
119338      */
119339     getColumnValue: function(column, summaryData){
119340         var comp     = Ext.getCmp(column.id),
119341             value    = summaryData[column.id],
119342             renderer = comp.summaryRenderer;
119343
119344         if (renderer) {
119345             value = renderer.call(
119346                 comp.scope || this,
119347                 value,
119348                 summaryData,
119349                 column.dataIndex
119350             );
119351         }
119352         return value;
119353     },
119354     
119355     /**
119356      * Get the summary data for a field.
119357      * @private
119358      * @param {Ext.data.Store} store The store to get the data from
119359      * @param {String/Function} type The type of aggregation. If a function is specified it will
119360      * be passed to the stores aggregate function.
119361      * @param {String} field The field to aggregate on
119362      * @param {Boolean} group True to aggregate in grouped mode 
119363      * @return {Number/String/Object} See the return type for the store functions.
119364      */
119365     getSummary: function(store, type, field, group){
119366         if (type) {
119367             if (Ext.isFunction(type)) {
119368                 return store.aggregate(type, null, group);
119369             }
119370             
119371             switch (type) {
119372                 case 'count':
119373                     return store.count(group);
119374                 case 'min':
119375                     return store.min(field, group);
119376                 case 'max':
119377                     return store.max(field, group);
119378                 case 'sum':
119379                     return store.sum(field, group);
119380                 case 'average':
119381                     return store.average(field, group);
119382                 default:
119383                     return group ? {} : '';
119384                     
119385             }
119386         }
119387     }
119388     
119389 });
119390
119391 /**
119392  * @class Ext.grid.feature.Chunking
119393  * @extends Ext.grid.feature.Feature
119394  */
119395 Ext.define('Ext.grid.feature.Chunking', {
119396     extend: 'Ext.grid.feature.Feature',
119397     alias: 'feature.chunking',
119398     
119399     chunkSize: 20,
119400     rowHeight: Ext.isIE ? 27 : 26,
119401     visibleChunk: 0,
119402     hasFeatureEvent: false,
119403     attachEvents: function() {
119404         var grid = this.view.up('gridpanel'),
119405             scroller = grid.down('gridscroller[dock=right]');
119406         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
119407         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
119408     },
119409     
119410     onBodyScroll: function(e, t) {
119411         var view = this.view,
119412             top  = t.scrollTop,
119413             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
119414         if (nextChunk !== this.visibleChunk) {
119415         
119416             this.visibleChunk = nextChunk;
119417             view.refresh();
119418             view.el.dom.scrollTop = top;
119419             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
119420             view.el.dom.scrollTop = top;
119421         }
119422     },
119423     
119424     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
119425         var o = {
119426             fullWidth: orig.fullWidth,
119427             chunks: []
119428         },
119429         //headerCt = this.view.headerCt,
119430         //colums = headerCt.getColumnsForTpl(),
119431         recordCount = orig.rows.length,
119432         start = 0,
119433         i = 0,
119434         visibleChunk = this.visibleChunk,
119435         chunk,
119436         rows,
119437         chunkLength;
119438
119439         for (; start < recordCount; start+=this.chunkSize, i++) {
119440             if (start+this.chunkSize > recordCount) {
119441                 chunkLength = recordCount - start;
119442             } else {
119443                 chunkLength = this.chunkSize;
119444             }
119445             
119446             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
119447                 rows = orig.rows.slice(start, start+this.chunkSize);
119448             } else {
119449                 rows = [];
119450             }
119451             o.chunks.push({
119452                 rows: rows,
119453                 fullWidth: fullWidth,
119454                 chunkHeight: chunkLength * this.rowHeight
119455             });
119456         }
119457         
119458         
119459         return o;
119460     },
119461     
119462     getTableFragments: function() {
119463         return {
119464             openTableWrap: function() {
119465                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
119466             },
119467             closeTableWrap: function() {
119468                 return '</div></tpl>';
119469             }
119470         };
119471     }
119472 });
119473
119474 /**
119475  * @class Ext.grid.feature.Grouping
119476  * @extends Ext.grid.feature.Feature
119477  * 
119478  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
119479  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
119480  * underneath. The groups can also be expanded and collapsed.
119481  * 
119482  * ## Extra Events
119483  * This feature adds several extra events that will be fired on the grid to interact with the groups:
119484  *
119485  *  - {@link #groupclick}
119486  *  - {@link #groupdblclick}
119487  *  - {@link #groupcontextmenu}
119488  *  - {@link #groupexpand}
119489  *  - {@link #groupcollapse}
119490  * 
119491  * ## Menu Augmentation
119492  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
119493  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
119494  * by thew user is {@link #enableNoGroups}.
119495  * 
119496  * ## Controlling Group Text
119497  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
119498  * the default display.
119499  * 
119500  * ## Example Usage
119501  * 
119502  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
119503  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
119504  *         startCollapsed: true // start all groups collapsed
119505  *     });
119506  * 
119507  * @ftype grouping
119508  * @author Nicolas Ferrero
119509  */
119510 Ext.define('Ext.grid.feature.Grouping', {
119511     extend: 'Ext.grid.feature.Feature',
119512     alias: 'feature.grouping',
119513
119514     eventPrefix: 'group',
119515     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
119516
119517     constructor: function() {
119518         var me = this;
119519         
119520         me.collapsedState = {};
119521         me.callParent(arguments);
119522     },
119523     
119524     /**
119525      * @event groupclick
119526      * @param {Ext.view.Table} view
119527      * @param {HTMLElement} node
119528      * @param {String} group The name of the group
119529      * @param {Ext.EventObject} e
119530      */
119531
119532     /**
119533      * @event groupdblclick
119534      * @param {Ext.view.Table} view
119535      * @param {HTMLElement} node
119536      * @param {String} group The name of the group
119537      * @param {Ext.EventObject} e
119538      */
119539
119540     /**
119541      * @event groupcontextmenu
119542      * @param {Ext.view.Table} view
119543      * @param {HTMLElement} node
119544      * @param {String} group The name of the group
119545      * @param {Ext.EventObject} e
119546      */
119547
119548     /**
119549      * @event groupcollapse
119550      * @param {Ext.view.Table} view
119551      * @param {HTMLElement} node
119552      * @param {String} group The name of the group
119553      * @param {Ext.EventObject} e
119554      */
119555
119556     /**
119557      * @event groupexpand
119558      * @param {Ext.view.Table} view
119559      * @param {HTMLElement} node
119560      * @param {String} group The name of the group
119561      * @param {Ext.EventObject} e
119562      */
119563
119564     /**
119565      * @cfg {String} groupHeaderTpl
119566      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
119567      * Defaults to 'Group: {name}'
119568      */
119569     groupHeaderTpl: 'Group: {name}',
119570
119571     /**
119572      * @cfg {Number} depthToIndent
119573      * Number of pixels to indent per grouping level
119574      */
119575     depthToIndent: 17,
119576
119577     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
119578     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
119579
119580     /**
119581      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
119582      */
119583     groupByText : 'Group By This Field',
119584     /**
119585      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
119586      */
119587     showGroupsText : 'Show in Groups',
119588
119589     /**
119590      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
119591      */
119592     hideGroupedHeader : false,
119593
119594     /**
119595      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
119596      */
119597     startCollapsed : false,
119598
119599     /**
119600      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
119601      */
119602     enableGroupingMenu : true,
119603
119604     /**
119605      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
119606      */
119607     enableNoGroups : true,
119608     
119609     enable: function() {
119610         var me    = this,
119611             view  = me.view,
119612             store = view.store,
119613             groupToggleMenuItem;
119614             
119615         me.lastGroupField = me.getGroupField();
119616
119617         if (me.lastGroupIndex) {
119618             store.group(me.lastGroupIndex);
119619         }
119620         me.callParent();
119621         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
119622         groupToggleMenuItem.setChecked(true, true);
119623         me.refreshIf();
119624     },
119625
119626     disable: function() {
119627         var me    = this,
119628             view  = me.view,
119629             store = view.store,
119630             remote = store.remoteGroup,
119631             groupToggleMenuItem,
119632             lastGroup;
119633             
119634         lastGroup = store.groupers.first();
119635         if (lastGroup) {
119636             me.lastGroupIndex = lastGroup.property;
119637             me.block();
119638             store.clearGrouping();
119639             me.unblock();
119640         }
119641         
119642         me.callParent();
119643         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
119644         groupToggleMenuItem.setChecked(true, true);
119645         groupToggleMenuItem.setChecked(false, true);
119646         if (!remote) {
119647             view.refresh();
119648         }
119649     },
119650     
119651     refreshIf: function() {
119652         if (this.blockRefresh !== true) {
119653             this.view.refresh();
119654         }    
119655     },
119656
119657     getFeatureTpl: function(values, parent, x, xcount) {
119658         var me = this;
119659         
119660         return [
119661             '<tpl if="typeof rows !== \'undefined\'">',
119662                 // group row tpl
119663                 '<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>',
119664                 // this is the rowbody
119665                 '<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>',
119666             '</tpl>'
119667         ].join('');
119668     },
119669
119670     getFragmentTpl: function() {
119671         return {
119672             indentByDepth: this.indentByDepth,
119673             depthToIndent: this.depthToIndent
119674         };
119675     },
119676
119677     indentByDepth: function(values) {
119678         var depth = values.depth || 0;
119679         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
119680     },
119681
119682     // Containers holding these components are responsible for
119683     // destroying them, we are just deleting references.
119684     destroy: function() {
119685         var me = this;
119686         
119687         delete me.view;
119688         delete me.prunedHeader;
119689     },
119690
119691     // perhaps rename to afterViewRender
119692     attachEvents: function() {
119693         var me = this,
119694             view = me.view;
119695
119696         view.on({
119697             scope: me,
119698             groupclick: me.onGroupClick,
119699             rowfocus: me.onRowFocus
119700         });
119701         view.store.on('groupchange', me.onGroupChange, me);
119702
119703         me.pruneGroupedHeader();
119704
119705         if (me.enableGroupingMenu) {
119706             me.injectGroupingMenu();
119707         }
119708         me.lastGroupField = me.getGroupField();
119709         me.block();
119710         me.onGroupChange();
119711         me.unblock();
119712     },
119713     
119714     injectGroupingMenu: function() {
119715         var me       = this,
119716             view     = me.view,
119717             headerCt = view.headerCt;
119718         headerCt.showMenuBy = me.showMenuBy;
119719         headerCt.getMenuItems = me.getMenuItems();
119720     },
119721     
119722     showMenuBy: function(t, header) {
119723         var menu = this.getMenu(),
119724             groupMenuItem  = menu.down('#groupMenuItem'),
119725             groupableMth = header.groupable === false ?  'disable' : 'enable';
119726             
119727         groupMenuItem[groupableMth]();
119728         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
119729     },
119730     
119731     getMenuItems: function() {
119732         var me                 = this,
119733             groupByText        = me.groupByText,
119734             disabled           = me.disabled,
119735             showGroupsText     = me.showGroupsText,
119736             enableNoGroups     = me.enableNoGroups,
119737             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
119738             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
119739         
119740         // runs in the scope of headerCt
119741         return function() {
119742             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
119743             o.push('-', {
119744                 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
119745                 itemId: 'groupMenuItem',
119746                 text: groupByText,
119747                 handler: groupMenuItemClick
119748             });
119749             if (enableNoGroups) {
119750                 o.push({
119751                     itemId: 'groupToggleMenuItem',
119752                     text: showGroupsText,
119753                     checked: !disabled,
119754                     checkHandler: groupToggleMenuItemClick
119755                 });
119756             }
119757             return o;
119758         };
119759     },
119760
119761
119762     /**
119763      * Group by the header the user has clicked on.
119764      * @private
119765      */
119766     onGroupMenuItemClick: function(menuItem, e) {
119767         var me = this,
119768             menu = menuItem.parentMenu,
119769             hdr  = menu.activeHeader,
119770             view = me.view,
119771             store = view.store,
119772             remote = store.remoteGroup;
119773
119774         delete me.lastGroupIndex;
119775         me.block();
119776         me.enable();
119777         store.group(hdr.dataIndex);
119778         me.pruneGroupedHeader();
119779         me.unblock();
119780         if (!remote) {
119781             view.refresh();
119782         }  
119783     },
119784     
119785     block: function(){
119786         this.blockRefresh = this.view.blockRefresh = true;
119787     },
119788     
119789     unblock: function(){
119790         this.blockRefresh = this.view.blockRefresh = false;
119791     },
119792
119793     /**
119794      * Turn on and off grouping via the menu
119795      * @private
119796      */
119797     onGroupToggleMenuItemClick: function(menuItem, checked) {
119798         this[checked ? 'enable' : 'disable']();
119799     },
119800
119801     /**
119802      * Prunes the grouped header from the header container
119803      * @private
119804      */
119805     pruneGroupedHeader: function() {
119806         var me         = this,
119807             view       = me.view,
119808             store      = view.store,
119809             groupField = me.getGroupField(),
119810             headerCt   = view.headerCt,
119811             header     = headerCt.down('header[dataIndex=' + groupField + ']');
119812
119813         if (header) {
119814             if (me.prunedHeader) {
119815                 me.prunedHeader.show();
119816             }
119817             me.prunedHeader = header;
119818             header.hide();
119819         }
119820     },
119821
119822     getGroupField: function(){
119823         var group = this.view.store.groupers.first();
119824         if (group) {
119825             return group.property;    
119826         }
119827         return ''; 
119828     },
119829
119830     /**
119831      * When a row gains focus, expand the groups above it
119832      * @private
119833      */
119834     onRowFocus: function(rowIdx) {
119835         var node    = this.view.getNode(rowIdx),
119836             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
119837
119838         if (groupBd) {
119839             // for multiple level groups, should expand every groupBd
119840             // above
119841             this.expand(groupBd);
119842         }
119843     },
119844
119845     /**
119846      * Expand a group by the groupBody
119847      * @param {Ext.Element} groupBd
119848      * @private
119849      */
119850     expand: function(groupBd) {
119851         var me = this,
119852             view = me.view,
119853             grid = view.up('gridpanel'),
119854             groupBdDom = Ext.getDom(groupBd);
119855             
119856         me.collapsedState[groupBdDom.id] = false;
119857
119858         groupBd.removeCls(me.collapsedCls);
119859         groupBd.prev().removeCls(me.hdCollapsedCls);
119860
119861         grid.determineScrollbars();
119862         grid.invalidateScroller();
119863         view.fireEvent('groupexpand');
119864     },
119865
119866     /**
119867      * Collapse a group by the groupBody
119868      * @param {Ext.Element} groupBd
119869      * @private
119870      */
119871     collapse: function(groupBd) {
119872         var me = this,
119873             view = me.view,
119874             grid = view.up('gridpanel'),
119875             groupBdDom = Ext.getDom(groupBd);
119876             
119877         me.collapsedState[groupBdDom.id] = true;
119878
119879         groupBd.addCls(me.collapsedCls);
119880         groupBd.prev().addCls(me.hdCollapsedCls);
119881
119882         grid.determineScrollbars();
119883         grid.invalidateScroller();
119884         view.fireEvent('groupcollapse');
119885     },
119886     
119887     onGroupChange: function(){
119888         var me = this,
119889             field = me.getGroupField(),
119890             menuItem;
119891             
119892         if (me.hideGroupedHeader) {
119893             if (me.lastGroupField) {
119894                 menuItem = me.getMenuItem(me.lastGroupField);
119895                 if (menuItem) {
119896                     menuItem.setChecked(true);
119897                 }
119898             }
119899             if (field) {
119900                 menuItem = me.getMenuItem(field);
119901                 if (menuItem) {
119902                     menuItem.setChecked(false);
119903                 }
119904             }
119905         }
119906         if (me.blockRefresh !== true) {
119907             me.view.refresh();
119908         }
119909         me.lastGroupField = field;
119910     },
119911     
119912     /**
119913      * Gets the related menu item for a dataIndex
119914      * @private
119915      * @return {Ext.grid.header.Container} The header
119916      */
119917     getMenuItem: function(dataIndex){
119918         var view = this.view,
119919             header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
119920             menu = view.headerCt.getMenu();
119921             
119922         return menu.down('menuitem[headerId='+ header.id +']');
119923     },
119924
119925     /**
119926      * Toggle between expanded/collapsed state when clicking on
119927      * the group.
119928      * @private
119929      */
119930     onGroupClick: function(view, group, idx, foo, e) {
119931         var me = this,
119932             toggleCls = me.toggleCls,
119933             groupBd = Ext.fly(group.nextSibling, '_grouping');
119934
119935         if (groupBd.hasCls(me.collapsedCls)) {
119936             me.expand(groupBd);
119937         } else {
119938             me.collapse(groupBd);
119939         }
119940     },
119941
119942     // Injects isRow and closeRow into the metaRowTpl.
119943     getMetaRowTplFragments: function() {
119944         return {
119945             isRow: this.isRow,
119946             closeRow: this.closeRow
119947         };
119948     },
119949
119950     // injected into rowtpl and wrapped around metaRowTpl
119951     // becomes part of the standard tpl
119952     isRow: function() {
119953         return '<tpl if="typeof rows === \'undefined\'">';
119954     },
119955
119956     // injected into rowtpl and wrapped around metaRowTpl
119957     // becomes part of the standard tpl
119958     closeRow: function() {
119959         return '</tpl>';
119960     },
119961
119962     // isRow and closeRow are injected via getMetaRowTplFragments
119963     mutateMetaRowTpl: function(metaRowTpl) {
119964         metaRowTpl.unshift('{[this.isRow()]}');
119965         metaRowTpl.push('{[this.closeRow()]}');
119966     },
119967
119968     // injects an additional style attribute via tdAttrKey with the proper
119969     // amount of padding
119970     getAdditionalData: function(data, idx, record, orig) {
119971         var view = this.view,
119972             hCt  = view.headerCt,
119973             col  = hCt.items.getAt(0),
119974             o = {},
119975             tdAttrKey = col.id + '-tdAttr';
119976
119977         // maintain the current tdAttr that a user may ahve set.
119978         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
119979         o.collapsed = 'true';
119980         return o;
119981     },
119982
119983     // return matching preppedRecords
119984     getGroupRows: function(group, records, preppedRecords, fullWidth) {
119985         var me = this,
119986             children = group.children,
119987             rows = group.rows = [],
119988             view = me.view;
119989         group.viewId = view.id;
119990
119991         Ext.Array.each(records, function(record, idx) {
119992             if (Ext.Array.indexOf(children, record) != -1) {
119993                 rows.push(Ext.apply(preppedRecords[idx], {
119994                     depth: 1
119995                 }));
119996             }
119997         });
119998         delete group.children;
119999         group.fullWidth = fullWidth;
120000         if (me.collapsedState[view.id + '-gp-' + group.name]) {
120001             group.collapsedCls = me.collapsedCls;
120002             group.hdCollapsedCls = me.hdCollapsedCls;
120003         }
120004
120005         return group;
120006     },
120007
120008     // return the data in a grouped format.
120009     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
120010         var me    = this,
120011             store = me.view.store,
120012             groups;
120013             
120014         if (!me.disabled && store.isGrouped()) {
120015             groups = store.getGroups();
120016             Ext.Array.each(groups, function(group, idx){
120017                 me.getGroupRows(group, records, preppedRecords, fullWidth);
120018             }, me);
120019             return {
120020                 rows: groups,
120021                 fullWidth: fullWidth
120022             };
120023         }
120024         return o;
120025     },
120026     
120027     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
120028     // events that are fired on the view. Chose not to return the actual
120029     // group itself because of its expense and because developers can simply
120030     // grab the group via store.getGroups(groupName)
120031     getFireEventArgs: function(type, view, featureTarget, e) {
120032         var returnArray = [type, view, featureTarget],
120033             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
120034             groupBdId   = Ext.getDom(groupBd).id,
120035             prefix      = view.id + '-gp-',
120036             groupName   = groupBdId.substr(prefix.length);
120037         
120038         returnArray.push(groupName, e);
120039         
120040         return returnArray;
120041     }
120042 });
120043
120044 /**
120045  * @class Ext.grid.feature.GroupingSummary
120046  * @extends Ext.grid.feature.Grouping
120047  *
120048  * This feature adds an aggregate summary row at the bottom of each group that is provided
120049  * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
120050  *
120051  * ## Calculation
120052  *
120053  * The summary value needs to be calculated for each column in the grid. This is controlled
120054  * by the summaryType option specified on the column. There are several built in summary types,
120055  * which can be specified as a string on the column configuration. These call underlying methods
120056  * on the store:
120057  *
120058  *  - {@link Ext.data.Store#count count}
120059  *  - {@link Ext.data.Store#sum sum}
120060  *  - {@link Ext.data.Store#min min}
120061  *  - {@link Ext.data.Store#max max}
120062  *  - {@link Ext.data.Store#average average}
120063  *
120064  * Alternatively, the summaryType can be a function definition. If this is the case,
120065  * the function is called with an array of records to calculate the summary value.
120066  *
120067  * ## Rendering
120068  *
120069  * Similar to a column, the summary also supports a summaryRenderer function. This
120070  * summaryRenderer is called before displaying a value. The function is optional, if
120071  * not specified the default calculated value is shown. The summaryRenderer is called with:
120072  *
120073  *  - value {Object} - The calculated value.
120074  *  - summaryData {Object} - Contains all raw summary values for the row.
120075  *  - field {String} - The name of the field we are calculating
120076  *
120077  * ## Example Usage
120078  *
120079  *     @example
120080  *     Ext.define('TestResult', {
120081  *         extend: 'Ext.data.Model',
120082  *         fields: ['student', 'subject', {
120083  *             name: 'mark',
120084  *             type: 'int'
120085  *         }]
120086  *     });
120087  *
120088  *     Ext.create('Ext.grid.Panel', {
120089  *         width: 200,
120090  *         height: 240,
120091  *         renderTo: document.body,
120092  *         features: [{
120093  *             groupHeaderTpl: 'Subject: {name}',
120094  *             ftype: 'groupingsummary'
120095  *         }],
120096  *         store: {
120097  *             model: 'TestResult',
120098  *             groupField: 'subject',
120099  *             data: [{
120100  *                 student: 'Student 1',
120101  *                 subject: 'Math',
120102  *                 mark: 84
120103  *             },{
120104  *                 student: 'Student 1',
120105  *                 subject: 'Science',
120106  *                 mark: 72
120107  *             },{
120108  *                 student: 'Student 2',
120109  *                 subject: 'Math',
120110  *                 mark: 96
120111  *             },{
120112  *                 student: 'Student 2',
120113  *                 subject: 'Science',
120114  *                 mark: 68
120115  *             }]
120116  *         },
120117  *         columns: [{
120118  *             dataIndex: 'student',
120119  *             text: 'Name',
120120  *             summaryType: 'count',
120121  *             summaryRenderer: function(value){
120122  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
120123  *             }
120124  *         }, {
120125  *             dataIndex: 'mark',
120126  *             text: 'Mark',
120127  *             summaryType: 'average'
120128  *         }]
120129  *     });
120130  */
120131 Ext.define('Ext.grid.feature.GroupingSummary', {
120132
120133     /* Begin Definitions */
120134
120135     extend: 'Ext.grid.feature.Grouping',
120136
120137     alias: 'feature.groupingsummary',
120138
120139     mixins: {
120140         summary: 'Ext.grid.feature.AbstractSummary'
120141     },
120142
120143     /* End Definitions */
120144
120145
120146    /**
120147     * Modifies the row template to include the summary row.
120148     * @private
120149     * @return {String} The modified template
120150     */
120151    getFeatureTpl: function() {
120152         var tpl = this.callParent(arguments);
120153
120154         if (this.showSummaryRow) {
120155             // lop off the end </tpl> so we can attach it
120156             tpl = tpl.replace('</tpl>', '');
120157             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
120158         }
120159         return tpl;
120160     },
120161
120162     /**
120163      * Gets any fragments needed for the template.
120164      * @private
120165      * @return {Object} The fragments
120166      */
120167     getFragmentTpl: function() {
120168         var me = this,
120169             fragments = me.callParent();
120170
120171         Ext.apply(fragments, me.getSummaryFragments());
120172         if (me.showSummaryRow) {
120173             // this gets called before render, so we'll setup the data here.
120174             me.summaryGroups = me.view.store.getGroups();
120175             me.summaryData = me.generateSummaryData();
120176         }
120177         return fragments;
120178     },
120179
120180     /**
120181      * Gets the data for printing a template row
120182      * @private
120183      * @param {Number} index The index in the template
120184      * @return {Array} The template values
120185      */
120186     getPrintData: function(index){
120187         var me = this,
120188             columns = me.view.headerCt.getColumnsForTpl(),
120189             i = 0,
120190             length = columns.length,
120191             data = [],
120192             name = me.summaryGroups[index - 1].name,
120193             active = me.summaryData[name],
120194             column;
120195
120196         for (; i < length; ++i) {
120197             column = columns[i];
120198             column.gridSummaryValue = this.getColumnValue(column, active);
120199             data.push(column);
120200         }
120201         return data;
120202     },
120203
120204     /**
120205      * Generates all of the summary data to be used when processing the template
120206      * @private
120207      * @return {Object} The summary data
120208      */
120209     generateSummaryData: function(){
120210         var me = this,
120211             data = {},
120212             remoteData = {},
120213             store = me.view.store,
120214             groupField = this.getGroupField(),
120215             reader = store.proxy.reader,
120216             groups = me.summaryGroups,
120217             columns = me.view.headerCt.getColumnsForTpl(),
120218             remote,
120219             i,
120220             length,
120221             fieldData,
120222             root,
120223             key,
120224             comp;
120225
120226         for (i = 0, length = groups.length; i < length; ++i) {
120227             data[groups[i].name] = {};
120228         }
120229
120230         /**
120231          * @cfg {String} [remoteRoot=undefined]  The name of the property which contains the Array of
120232          * summary objects. It allows to use server-side calculated summaries.
120233          */
120234         if (me.remoteRoot && reader.rawData) {
120235             // reset reader root and rebuild extractors to extract summaries data
120236             root = reader.root;
120237             reader.root = me.remoteRoot;
120238             reader.buildExtractors(true);
120239             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
120240                  remoteData[value[groupField]] = value;
120241             });
120242             // restore initial reader configuration
120243             reader.root = root;
120244             reader.buildExtractors(true);
120245         }
120246
120247         for (i = 0, length = columns.length; i < length; ++i) {
120248             comp = Ext.getCmp(columns[i].id);
120249             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
120250
120251             for (key in fieldData) {
120252                 if (fieldData.hasOwnProperty(key)) {
120253                     data[key][comp.id] = fieldData[key];
120254                 }
120255             }
120256
120257             for (key in remoteData) {
120258                 if (remoteData.hasOwnProperty(key)) {
120259                     remote = remoteData[key][comp.dataIndex];
120260                     if (remote !== undefined && data[key] !== undefined) {
120261                         data[key][comp.id] = remote;
120262                     }
120263                 }
120264             }
120265         }
120266         return data;
120267     }
120268 });
120269
120270 /**
120271  * @class Ext.grid.feature.RowBody
120272  * @extends Ext.grid.feature.Feature
120273  *
120274  * The rowbody feature enhances the grid's markup to have an additional
120275  * tr -> td -> div which spans the entire width of the original row.
120276  *
120277  * This is useful to to associate additional information with a particular
120278  * record in a grid.
120279  *
120280  * Rowbodies are initially hidden unless you override getAdditionalData.
120281  *
120282  * Will expose additional events on the gridview with the prefix of 'rowbody'.
120283  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
120284  *
120285  * @ftype rowbody
120286  */
120287 Ext.define('Ext.grid.feature.RowBody', {
120288     extend: 'Ext.grid.feature.Feature',
120289     alias: 'feature.rowbody',
120290     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
120291     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
120292     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
120293     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
120294
120295     eventPrefix: 'rowbody',
120296     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
120297     
120298     getRowBody: function(values) {
120299         return [
120300             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
120301                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
120302                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
120303                 '</td>',
120304             '</tr>'
120305         ].join('');
120306     },
120307     
120308     // injects getRowBody into the metaRowTpl.
120309     getMetaRowTplFragments: function() {
120310         return {
120311             getRowBody: this.getRowBody,
120312             rowBodyTrCls: this.rowBodyTrCls,
120313             rowBodyTdCls: this.rowBodyTdCls,
120314             rowBodyDivCls: this.rowBodyDivCls
120315         };
120316     },
120317
120318     mutateMetaRowTpl: function(metaRowTpl) {
120319         metaRowTpl.push('{[this.getRowBody(values)]}');
120320     },
120321
120322     /**
120323      * Provide additional data to the prepareData call within the grid view.
120324      * The rowbody feature adds 3 additional variables into the grid view's template.
120325      * These are rowBodyCls, rowBodyColspan, and rowBody.
120326      * @param {Object} data The data for this particular record.
120327      * @param {Number} idx The row index for this record.
120328      * @param {Ext.data.Model} record The record instance
120329      * @param {Object} orig The original result from the prepareData call to massage.
120330      */
120331     getAdditionalData: function(data, idx, record, orig) {
120332         var headerCt = this.view.headerCt,
120333             colspan  = headerCt.getColumnCount();
120334
120335         return {
120336             rowBody: "",
120337             rowBodyCls: this.rowBodyCls,
120338             rowBodyColspan: colspan
120339         };
120340     }
120341 });
120342 /**
120343  * @class Ext.grid.feature.RowWrap
120344  * @extends Ext.grid.feature.Feature
120345  * @private
120346  */
120347 Ext.define('Ext.grid.feature.RowWrap', {
120348     extend: 'Ext.grid.feature.Feature',
120349     alias: 'feature.rowwrap',
120350
120351     // turn off feature events.
120352     hasFeatureEvent: false,
120353     
120354     mutateMetaRowTpl: function(metaRowTpl) {        
120355         // Remove "x-grid-row" from the first row, note this could be wrong
120356         // if some other feature unshifted things in front.
120357         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
120358         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
120359         // 2
120360         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
120361         // 1
120362         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
120363         
120364         // 3
120365         metaRowTpl.push('</table>');
120366         // 4
120367         metaRowTpl.push('</div></td></tr>');
120368     },
120369     
120370     embedColSpan: function() {
120371         return '{colspan}';
120372     },
120373     
120374     embedFullWidth: function() {
120375         return '{fullWidth}';
120376     },
120377     
120378     getAdditionalData: function(data, idx, record, orig) {
120379         var headerCt = this.view.headerCt,
120380             colspan  = headerCt.getColumnCount(),
120381             fullWidth = headerCt.getFullWidth(),
120382             items    = headerCt.query('gridcolumn'),
120383             itemsLn  = items.length,
120384             i = 0,
120385             o = {
120386                 colspan: colspan,
120387                 fullWidth: fullWidth
120388             },
120389             id,
120390             tdClsKey,
120391             colResizerCls;
120392
120393         for (; i < itemsLn; i++) {
120394             id = items[i].id;
120395             tdClsKey = id + '-tdCls';
120396             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
120397             // give the inner td's the resizer class
120398             // while maintaining anything a user may have injected via a custom
120399             // renderer
120400             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
120401             // TODO: Unhackify the initial rendering width's
120402             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
120403             if (orig[id+'-tdAttr']) {
120404                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
120405             }
120406             
120407         }
120408
120409         return o;
120410     },
120411     
120412     getMetaRowTplFragments: function() {
120413         return {
120414             embedFullWidth: this.embedFullWidth,
120415             embedColSpan: this.embedColSpan
120416         };
120417     }
120418     
120419 });
120420 /**
120421  * @class Ext.grid.feature.Summary
120422  * @extends Ext.grid.feature.AbstractSummary
120423  * 
120424  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
120425  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
120426  * calculation and rendering.
120427  * 
120428  * ## Calculation
120429  * The summary value needs to be calculated for each column in the grid. This is controlled
120430  * by the summaryType option specified on the column. There are several built in summary types,
120431  * which can be specified as a string on the column configuration. These call underlying methods
120432  * on the store:
120433  *
120434  *  - {@link Ext.data.Store#count count}
120435  *  - {@link Ext.data.Store#sum sum}
120436  *  - {@link Ext.data.Store#min min}
120437  *  - {@link Ext.data.Store#max max}
120438  *  - {@link Ext.data.Store#average average}
120439  *
120440  * Alternatively, the summaryType can be a function definition. If this is the case,
120441  * the function is called with an array of records to calculate the summary value.
120442  * 
120443  * ## Rendering
120444  * Similar to a column, the summary also supports a summaryRenderer function. This
120445  * summaryRenderer is called before displaying a value. The function is optional, if
120446  * not specified the default calculated value is shown. The summaryRenderer is called with:
120447  *
120448  *  - value {Object} - The calculated value.
120449  *  - summaryData {Object} - Contains all raw summary values for the row.
120450  *  - field {String} - The name of the field we are calculating
120451  * 
120452  * ## Example Usage
120453  *
120454  *     @example
120455  *     Ext.define('TestResult', {
120456  *         extend: 'Ext.data.Model',
120457  *         fields: ['student', {
120458  *             name: 'mark',
120459  *             type: 'int'
120460  *         }]
120461  *     });
120462  *     
120463  *     Ext.create('Ext.grid.Panel', {
120464  *         width: 200,
120465  *         height: 140,
120466  *         renderTo: document.body,
120467  *         features: [{
120468  *             ftype: 'summary'
120469  *         }],
120470  *         store: {
120471  *             model: 'TestResult',
120472  *             data: [{
120473  *                 student: 'Student 1',
120474  *                 mark: 84
120475  *             },{
120476  *                 student: 'Student 2',
120477  *                 mark: 72
120478  *             },{
120479  *                 student: 'Student 3',
120480  *                 mark: 96
120481  *             },{
120482  *                 student: 'Student 4',
120483  *                 mark: 68
120484  *             }]
120485  *         },
120486  *         columns: [{
120487  *             dataIndex: 'student',
120488  *             text: 'Name',
120489  *             summaryType: 'count',
120490  *             summaryRenderer: function(value, summaryData, dataIndex) {
120491  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
120492  *             }
120493  *         }, {
120494  *             dataIndex: 'mark',
120495  *             text: 'Mark',
120496  *             summaryType: 'average'
120497  *         }]
120498  *     });
120499  */
120500 Ext.define('Ext.grid.feature.Summary', {
120501     
120502     /* Begin Definitions */
120503     
120504     extend: 'Ext.grid.feature.AbstractSummary',
120505     
120506     alias: 'feature.summary',
120507     
120508     /* End Definitions */
120509     
120510     /**
120511      * Gets any fragments needed for the template.
120512      * @private
120513      * @return {Object} The fragments
120514      */
120515     getFragmentTpl: function() {
120516         // this gets called before render, so we'll setup the data here.
120517         this.summaryData = this.generateSummaryData(); 
120518         return this.getSummaryFragments();
120519     },
120520     
120521     /**
120522      * Overrides the closeRows method on the template so we can include our own custom
120523      * footer.
120524      * @private
120525      * @return {Object} The custom fragments
120526      */
120527     getTableFragments: function(){
120528         if (this.showSummaryRow) {
120529             return {
120530                 closeRows: this.closeRows
120531             };
120532         }
120533     },
120534     
120535     /**
120536      * Provide our own custom footer for the grid.
120537      * @private
120538      * @return {String} The custom footer
120539      */
120540     closeRows: function() {
120541         return '</tpl>{[this.printSummaryRow()]}';
120542     },
120543     
120544     /**
120545      * Gets the data for printing a template row
120546      * @private
120547      * @param {Number} index The index in the template
120548      * @return {Array} The template values
120549      */
120550     getPrintData: function(index){
120551         var me = this,
120552             columns = me.view.headerCt.getColumnsForTpl(),
120553             i = 0,
120554             length = columns.length,
120555             data = [],
120556             active = me.summaryData,
120557             column;
120558             
120559         for (; i < length; ++i) {
120560             column = columns[i];
120561             column.gridSummaryValue = this.getColumnValue(column, active);
120562             data.push(column);
120563         }
120564         return data;
120565     },
120566     
120567     /**
120568      * Generates all of the summary data to be used when processing the template
120569      * @private
120570      * @return {Object} The summary data
120571      */
120572     generateSummaryData: function(){
120573         var me = this,
120574             data = {},
120575             store = me.view.store,
120576             columns = me.view.headerCt.getColumnsForTpl(),
120577             i = 0,
120578             length = columns.length,
120579             fieldData,
120580             key,
120581             comp;
120582             
120583         for (i = 0, length = columns.length; i < length; ++i) {
120584             comp = Ext.getCmp(columns[i].id);
120585             data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
120586         }
120587         return data;
120588     }
120589 });
120590 /**
120591  * @class Ext.grid.header.DragZone
120592  * @extends Ext.dd.DragZone
120593  * @private
120594  */
120595 Ext.define('Ext.grid.header.DragZone', {
120596     extend: 'Ext.dd.DragZone',
120597     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
120598     maxProxyWidth: 120,
120599
120600     constructor: function(headerCt) {
120601         this.headerCt = headerCt;
120602         this.ddGroup =  this.getDDGroup();
120603         this.callParent([headerCt.el]);
120604         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
120605     },
120606
120607     getDDGroup: function() {
120608         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
120609     },
120610
120611     getDragData: function(e) {
120612         var header = e.getTarget('.'+this.colHeaderCls),
120613             headerCmp;
120614
120615         if (header) {
120616             headerCmp = Ext.getCmp(header.id);
120617             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
120618                 var ddel = document.createElement('div');
120619                 ddel.innerHTML = Ext.getCmp(header.id).text;
120620                 return {
120621                     ddel: ddel,
120622                     header: headerCmp
120623                 };
120624             }
120625         }
120626         return false;
120627     },
120628
120629     onBeforeDrag: function() {
120630         return !(this.headerCt.dragging || this.disabled);
120631     },
120632
120633     onInitDrag: function() {
120634         this.headerCt.dragging = true;
120635         this.callParent(arguments);
120636     },
120637
120638     onDragDrop: function() {
120639         this.headerCt.dragging = false;
120640         this.callParent(arguments);
120641     },
120642
120643     afterRepair: function() {
120644         this.callParent();
120645         this.headerCt.dragging = false;
120646     },
120647
120648     getRepairXY: function() {
120649         return this.dragData.header.el.getXY();
120650     },
120651     
120652     disable: function() {
120653         this.disabled = true;
120654     },
120655     
120656     enable: function() {
120657         this.disabled = false;
120658     }
120659 });
120660
120661 /**
120662  * @class Ext.grid.header.DropZone
120663  * @extends Ext.dd.DropZone
120664  * @private
120665  */
120666 Ext.define('Ext.grid.header.DropZone', {
120667     extend: 'Ext.dd.DropZone',
120668     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
120669     proxyOffsets: [-4, -9],
120670
120671     constructor: function(headerCt){
120672         this.headerCt = headerCt;
120673         this.ddGroup = this.getDDGroup();
120674         this.callParent([headerCt.el]);
120675     },
120676
120677     getDDGroup: function() {
120678         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
120679     },
120680
120681     getTargetFromEvent : function(e){
120682         return e.getTarget('.' + this.colHeaderCls);
120683     },
120684
120685     getTopIndicator: function() {
120686         if (!this.topIndicator) {
120687             this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
120688                 cls: "col-move-top",
120689                 html: "&#160;"
120690             }, true);
120691         }
120692         return this.topIndicator;
120693     },
120694
120695     getBottomIndicator: function() {
120696         if (!this.bottomIndicator) {
120697             this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
120698                 cls: "col-move-bottom",
120699                 html: "&#160;"
120700             }, true);
120701         }
120702         return this.bottomIndicator;
120703     },
120704
120705     getLocation: function(e, t) {
120706         var x      = e.getXY()[0],
120707             region = Ext.fly(t).getRegion(),
120708             pos, header;
120709
120710         if ((region.right - x) <= (region.right - region.left) / 2) {
120711             pos = "after";
120712         } else {
120713             pos = "before";
120714         }
120715         return {
120716             pos: pos,
120717             header: Ext.getCmp(t.id),
120718             node: t
120719         };
120720     },
120721
120722     positionIndicator: function(draggedHeader, node, e){
120723         var location = this.getLocation(e, node),
120724             header = location.header,
120725             pos    = location.pos,
120726             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
120727             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
120728             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
120729             topXY, bottomXY, headerCtEl, minX, maxX;
120730
120731         // Cannot drag beyond non-draggable start column
120732         if (!header.draggable && header.getIndex() == 0) {
120733             return false;
120734         }
120735
120736         this.lastLocation = location;
120737
120738         if ((draggedHeader !== header) &&
120739             ((pos === "before" && nextHd !== header) ||
120740             (pos === "after" && prevHd !== header)) &&
120741             !header.isDescendantOf(draggedHeader)) {
120742
120743             // As we move in between different DropZones that are in the same
120744             // group (such as the case when in a locked grid), invalidateDrop
120745             // on the other dropZones.
120746             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
120747                 ln = allDropZones.length,
120748                 i  = 0,
120749                 dropZone;
120750
120751             for (; i < ln; i++) {
120752                 dropZone = allDropZones[i];
120753                 if (dropZone !== this && dropZone.invalidateDrop) {
120754                     dropZone.invalidateDrop();
120755                 }
120756             }
120757
120758
120759             this.valid = true;
120760             topIndicator = this.getTopIndicator();
120761             bottomIndicator = this.getBottomIndicator();
120762             if (pos === 'before') {
120763                 topAnchor = 'tl';
120764                 bottomAnchor = 'bl';
120765             } else {
120766                 topAnchor = 'tr';
120767                 bottomAnchor = 'br';
120768             }
120769             topXY = header.el.getAnchorXY(topAnchor);
120770             bottomXY = header.el.getAnchorXY(bottomAnchor);
120771
120772             // constrain the indicators to the viewable section
120773             headerCtEl = this.headerCt.el;
120774             minX = headerCtEl.getLeft();
120775             maxX = headerCtEl.getRight();
120776
120777             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
120778             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
120779
120780             // adjust by offsets, this is to center the arrows so that they point
120781             // at the split point
120782             topXY[0] -= 4;
120783             topXY[1] -= 9;
120784             bottomXY[0] -= 4;
120785
120786             // position and show indicators
120787             topIndicator.setXY(topXY);
120788             bottomIndicator.setXY(bottomXY);
120789             topIndicator.show();
120790             bottomIndicator.show();
120791         // invalidate drop operation and hide indicators
120792         } else {
120793             this.invalidateDrop();
120794         }
120795     },
120796
120797     invalidateDrop: function() {
120798         this.valid = false;
120799         this.hideIndicators();
120800     },
120801
120802     onNodeOver: function(node, dragZone, e, data) {
120803         if (data.header.el.dom !== node) {
120804             this.positionIndicator(data.header, node, e);
120805         }
120806         return this.valid ? this.dropAllowed : this.dropNotAllowed;
120807     },
120808
120809     hideIndicators: function() {
120810         this.getTopIndicator().hide();
120811         this.getBottomIndicator().hide();
120812     },
120813
120814     onNodeOut: function() {
120815         this.hideIndicators();
120816     },
120817
120818     onNodeDrop: function(node, dragZone, e, data) {
120819         if (this.valid) {
120820             this.invalidateDrop();
120821             var hd = data.header,
120822                 lastLocation = this.lastLocation,
120823                 fromCt  = hd.ownerCt,
120824                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
120825                 toCt    = lastLocation.header.ownerCt,
120826                 toIdx   = toCt.items.indexOf(lastLocation.header),
120827                 headerCt = this.headerCt,
120828                 groupCt,
120829                 scrollerOwner;
120830
120831             if (lastLocation.pos === 'after') {
120832                 toIdx++;
120833             }
120834
120835             // If we are dragging in between two HeaderContainers that have had the lockable
120836             // mixin injected we will lock/unlock headers in between sections. Note that lockable
120837             // does NOT currently support grouped headers.
120838             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
120839                 scrollerOwner = fromCt.up('[scrollerOwner]');
120840                 scrollerOwner.lock(hd, toIdx);
120841             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
120842                 scrollerOwner = fromCt.up('[scrollerOwner]');
120843                 scrollerOwner.unlock(hd, toIdx);
120844             } else {
120845                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
120846                 // in between the same container.
120847                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
120848                     toIdx--;
120849                 }
120850
120851                 // Remove dragged header from where it was without destroying it or relaying its Container
120852                 if (fromCt !== toCt) {
120853                     fromCt.suspendLayout = true;
120854                     fromCt.remove(hd, false);
120855                     fromCt.suspendLayout = false;
120856                 }
120857
120858                 // Dragged the last header out of the fromCt group... The fromCt group must die
120859                 if (fromCt.isGroupHeader) {
120860                     if (!fromCt.items.getCount()) {
120861                         groupCt = fromCt.ownerCt;
120862                         groupCt.suspendLayout = true;
120863                         groupCt.remove(fromCt, false);
120864                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
120865                         groupCt.suspendLayout = false;
120866                     } else {
120867                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
120868                         fromCt.setWidth(fromCt.minWidth);
120869                     }
120870                 }
120871
120872                 // Move dragged header into its drop position
120873                 toCt.suspendLayout = true;
120874                 if (fromCt === toCt) {
120875                     toCt.move(fromIdx, toIdx);
120876                 } else {
120877                     toCt.insert(toIdx, hd);
120878                 }
120879                 toCt.suspendLayout = false;
120880
120881                 // Group headers acquire the aggregate width of their child headers
120882                 // Therefore a child header may not flex; it must contribute a fixed width.
120883                 // But we restore the flex value when moving back into the main header container
120884                 if (toCt.isGroupHeader) {
120885                     hd.savedFlex = hd.flex;
120886                     delete hd.flex;
120887                     hd.width = hd.getWidth();
120888                     // When there was previously a flex, we need to ensure we don't count for the
120889                     // border twice.
120890                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
120891                     toCt.setWidth(toCt.minWidth);
120892                 } else {
120893                     if (hd.savedFlex) {
120894                         hd.flex = hd.savedFlex;
120895                         delete hd.width;
120896                     }
120897                 }
120898
120899
120900                 // Refresh columns cache in case we remove an emptied group column
120901                 headerCt.purgeCache();
120902                 headerCt.doLayout();
120903                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
120904                 // Emptied group header can only be destroyed after the header and grid have been refreshed
120905                 if (!fromCt.items.getCount()) {
120906                     fromCt.destroy();
120907                 }
120908             }
120909         }
120910     }
120911 });
120912
120913 /**
120914  * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
120915  * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
120916  * in the {@link Ext.grid.column.Column column configuration}.
120917  *
120918  * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
120919  * {@link Ext.grid.plugin.RowEditing}.
120920  */
120921 Ext.define('Ext.grid.plugin.Editing', {
120922     alias: 'editing.editing',
120923
120924     requires: [
120925         'Ext.grid.column.Column',
120926         'Ext.util.KeyNav'
120927     ],
120928
120929     mixins: {
120930         observable: 'Ext.util.Observable'
120931     },
120932
120933     /**
120934      * @cfg {Number} clicksToEdit
120935      * The number of clicks on a grid required to display the editor.
120936      */
120937     clicksToEdit: 2,
120938
120939     // private
120940     defaultFieldXType: 'textfield',
120941
120942     // cell, row, form
120943     editStyle: '',
120944
120945     constructor: function(config) {
120946         var me = this;
120947         Ext.apply(me, config);
120948
120949         me.addEvents(
120950             // Doc'ed in separate editing plugins
120951             'beforeedit',
120952
120953             // Doc'ed in separate editing plugins
120954             'edit',
120955
120956             // Doc'ed in separate editing plugins
120957             'validateedit'
120958         );
120959         me.mixins.observable.constructor.call(me);
120960         // TODO: Deprecated, remove in 5.0
120961         me.relayEvents(me, ['afteredit'], 'after');
120962     },
120963
120964     // private
120965     init: function(grid) {
120966         var me = this;
120967
120968         me.grid = grid;
120969         me.view = grid.view;
120970         me.initEvents();
120971         me.mon(grid, 'reconfigure', me.onReconfigure, me);
120972         me.onReconfigure();
120973
120974         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
120975         // Marks the grid as editable, so that the SelectionModel
120976         // can make appropriate decisions during navigation
120977         grid.isEditable = true;
120978         grid.editingPlugin = grid.view.editingPlugin = me;
120979     },
120980
120981     /**
120982      * Fires after the grid is reconfigured
120983      * @private
120984      */
120985     onReconfigure: function(){
120986         this.initFieldAccessors(this.view.getGridColumns());
120987     },
120988
120989     /**
120990      * @private
120991      * AbstractComponent calls destroy on all its plugins at destroy time.
120992      */
120993     destroy: function() {
120994         var me = this,
120995             grid = me.grid,
120996             headerCt = grid.headerCt,
120997             events = grid.events;
120998
120999         Ext.destroy(me.keyNav);
121000         me.removeFieldAccessors(grid.getView().getGridColumns());
121001
121002         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
121003         me.clearListeners();
121004
121005         delete me.grid.editingPlugin;
121006         delete me.grid.view.editingPlugin;
121007         delete me.grid;
121008         delete me.view;
121009         delete me.editor;
121010         delete me.keyNav;
121011     },
121012
121013     // private
121014     getEditStyle: function() {
121015         return this.editStyle;
121016     },
121017
121018     // private
121019     initFieldAccessors: function(column) {
121020         var me = this;
121021
121022         if (Ext.isArray(column)) {
121023             Ext.Array.forEach(column, me.initFieldAccessors, me);
121024             return;
121025         }
121026
121027         // Augment the Header class to have a getEditor and setEditor method
121028         // Important: Only if the header does not have its own implementation.
121029         Ext.applyIf(column, {
121030             getEditor: function(record, defaultField) {
121031                 return me.getColumnField(this, defaultField);
121032             },
121033
121034             setEditor: function(field) {
121035                 me.setColumnField(this, field);
121036             }
121037         });
121038     },
121039
121040     // private
121041     removeFieldAccessors: function(column) {
121042         var me = this;
121043
121044         if (Ext.isArray(column)) {
121045             Ext.Array.forEach(column, me.removeFieldAccessors, me);
121046             return;
121047         }
121048
121049         delete column.getEditor;
121050         delete column.setEditor;
121051     },
121052
121053     // private
121054     // remaps to the public API of Ext.grid.column.Column.getEditor
121055     getColumnField: function(columnHeader, defaultField) {
121056         var field = columnHeader.field;
121057
121058         if (!field && columnHeader.editor) {
121059             field = columnHeader.editor;
121060             delete columnHeader.editor;
121061         }
121062
121063         if (!field && defaultField) {
121064             field = defaultField;
121065         }
121066
121067         if (field) {
121068             if (Ext.isString(field)) {
121069                 field = { xtype: field };
121070             }
121071             if (Ext.isObject(field) && !field.isFormField) {
121072                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
121073                 columnHeader.field = field;
121074             }
121075
121076             Ext.apply(field, {
121077                 name: columnHeader.dataIndex
121078             });
121079
121080             return field;
121081         }
121082     },
121083
121084     // private
121085     // remaps to the public API of Ext.grid.column.Column.setEditor
121086     setColumnField: function(column, field) {
121087         if (Ext.isObject(field) && !field.isFormField) {
121088             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
121089         }
121090         column.field = field;
121091     },
121092
121093     // private
121094     initEvents: function() {
121095         var me = this;
121096         me.initEditTriggers();
121097         me.initCancelTriggers();
121098     },
121099
121100     // @abstract
121101     initCancelTriggers: Ext.emptyFn,
121102
121103     // private
121104     initEditTriggers: function() {
121105         var me = this,
121106             view = me.view,
121107             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
121108
121109         // Start editing
121110         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
121111         view.on('render', function() {
121112             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
121113                 enter: me.onEnterKey,
121114                 esc: me.onEscKey,
121115                 scope: me
121116             });
121117         }, me, { single: true });
121118     },
121119
121120     // private
121121     onEnterKey: function(e) {
121122         var me = this,
121123             grid = me.grid,
121124             selModel = grid.getSelectionModel(),
121125             record,
121126             columnHeader = grid.headerCt.getHeaderAtIndex(0);
121127
121128         // Calculate editing start position from SelectionModel
121129         // CellSelectionModel
121130         if (selModel.getCurrentPosition) {
121131             pos = selModel.getCurrentPosition();
121132             record = grid.store.getAt(pos.row);
121133             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
121134         }
121135         // RowSelectionModel
121136         else {
121137             record = selModel.getLastSelected();
121138         }
121139         me.startEdit(record, columnHeader);
121140     },
121141
121142     // private
121143     onEscKey: function(e) {
121144         this.cancelEdit();
121145     },
121146
121147     // private
121148     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
121149         this.startEdit(record, view.getHeaderAtIndex(colIdx));
121150     },
121151
121152     /**
121153      * @private
121154      * @template
121155      * Template method called before editing begins.
121156      * @param {Object} context The current editing context
121157      * @return {Boolean} Return false to cancel the editing process
121158      */
121159     beforeEdit: Ext.emptyFn,
121160
121161     /**
121162      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121163      * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
121164      * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
121165      */
121166     startEdit: function(record, columnHeader) {
121167         var me = this,
121168             context = me.getEditingContext(record, columnHeader);
121169
121170         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
121171             return false;
121172         }
121173
121174         me.context = context;
121175         me.editing = true;
121176     },
121177
121178     /**
121179      * @private
121180      * Collects all information necessary for any subclasses to perform their editing functions.
121181      * @param record
121182      * @param columnHeader
121183      * @returns {Object} The editing context based upon the passed record and column
121184      */
121185     getEditingContext: function(record, columnHeader) {
121186         var me = this,
121187             grid = me.grid,
121188             store = grid.store,
121189             rowIdx,
121190             colIdx,
121191             view = grid.getView(),
121192             value;
121193
121194         // If they'd passed numeric row, column indices, look them up.
121195         if (Ext.isNumber(record)) {
121196             rowIdx = record;
121197             record = store.getAt(rowIdx);
121198         } else {
121199             rowIdx = store.indexOf(record);
121200         }
121201         if (Ext.isNumber(columnHeader)) {
121202             colIdx = columnHeader;
121203             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
121204         } else {
121205             colIdx = columnHeader.getIndex();
121206         }
121207
121208         value = record.get(columnHeader.dataIndex);
121209         return {
121210             grid: grid,
121211             record: record,
121212             field: columnHeader.dataIndex,
121213             value: value,
121214             row: view.getNode(rowIdx),
121215             column: columnHeader,
121216             rowIdx: rowIdx,
121217             colIdx: colIdx
121218         };
121219     },
121220
121221     /**
121222      * Cancels any active edit that is in progress.
121223      */
121224     cancelEdit: function() {
121225         this.editing = false;
121226     },
121227
121228     /**
121229      * Completes the edit if there is an active edit in progress.
121230      */
121231     completeEdit: function() {
121232         var me = this;
121233
121234         if (me.editing && me.validateEdit()) {
121235             me.fireEvent('edit', me.context);
121236         }
121237
121238         delete me.context;
121239         me.editing = false;
121240     },
121241
121242     // @abstract
121243     validateEdit: function() {
121244         var me = this,
121245             context = me.context;
121246
121247         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
121248     }
121249 });
121250
121251 /**
121252  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
121253  * cell will be editable at a time. The field that will be used for the editor is defined at the
121254  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
121255  *
121256  * If an editor is not specified for a particular column then that cell will not be editable and it will
121257  * be skipped when activated via the mouse or the keyboard.
121258  *
121259  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
121260  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
121261  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
121262  *
121263  *     @example
121264  *     Ext.create('Ext.data.Store', {
121265  *         storeId:'simpsonsStore',
121266  *         fields:['name', 'email', 'phone'],
121267  *         data:{'items':[
121268  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
121269  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
121270  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
121271  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
121272  *         ]},
121273  *         proxy: {
121274  *             type: 'memory',
121275  *             reader: {
121276  *                 type: 'json',
121277  *                 root: 'items'
121278  *             }
121279  *         }
121280  *     });
121281  *
121282  *     Ext.create('Ext.grid.Panel', {
121283  *         title: 'Simpsons',
121284  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
121285  *         columns: [
121286  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
121287  *             {header: 'Email', dataIndex: 'email', flex:1,
121288  *                 editor: {
121289  *                     xtype: 'textfield',
121290  *                     allowBlank: false
121291  *                 }
121292  *             },
121293  *             {header: 'Phone', dataIndex: 'phone'}
121294  *         ],
121295  *         selType: 'cellmodel',
121296  *         plugins: [
121297  *             Ext.create('Ext.grid.plugin.CellEditing', {
121298  *                 clicksToEdit: 1
121299  *             })
121300  *         ],
121301  *         height: 200,
121302  *         width: 400,
121303  *         renderTo: Ext.getBody()
121304  *     });
121305  */
121306 Ext.define('Ext.grid.plugin.CellEditing', {
121307     alias: 'plugin.cellediting',
121308     extend: 'Ext.grid.plugin.Editing',
121309     requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
121310
121311     constructor: function() {
121312         /**
121313          * @event beforeedit
121314          * Fires before cell editing is triggered. Return false from event handler to stop the editing.
121315          *
121316          * @param {Object} e An edit event with the following properties:
121317          *
121318          * - grid - The grid
121319          * - record - The record being edited
121320          * - field - The field name being edited
121321          * - value - The value for the field being edited.
121322          * - row - The grid table row
121323          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
121324          * - rowIdx - The row index that is being edited
121325          * - colIdx - The column index that is being edited
121326          * - cancel - Set this to true to cancel the edit or return false from your handler.
121327          */
121328         /**
121329          * @event edit
121330          * Fires after a cell is edited. Usage example:
121331          *
121332          *     grid.on('edit', function(editor, e) {
121333          *         // commit the changes right after editing finished
121334          *         e.record.commit();
121335          *     };
121336          *
121337          * @param {Ext.grid.plugin.Editing} editor
121338          * @param {Object} e An edit event with the following properties:
121339          *
121340          * - grid - The grid
121341          * - record - The record that was edited
121342          * - field - The field name that was edited
121343          * - value - The value being set
121344          * - originalValue - The original value for the field, before the edit.
121345          * - row - The grid table row
121346          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
121347          * - rowIdx - The row index that was edited
121348          * - colIdx - The column index that was edited
121349          */
121350         /**
121351          * @event validateedit
121352          * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
121353          * cancel the change.
121354          *
121355          * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
121356          * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
121357          * example) and then setting the field's new value in the Record directly:
121358          *
121359          *     grid.on('validateedit', function(editor, e) {
121360          *       var myTargetRow = 6;
121361          *
121362          *       if (e.row == myTargetRow) {
121363          *         e.cancel = true;
121364          *         e.record.data[e.field] = e.value;
121365          *       }
121366          *     });
121367          *
121368          * @param {Ext.grid.plugin.Editing} editor
121369          * @param {Object} e An edit event with the following properties:
121370          *
121371          * - grid - The grid
121372          * - record - The record being edited
121373          * - field - The field name being edited
121374          * - value - The value being set
121375          * - originalValue - The original value for the field, before the edit.
121376          * - row - The grid table row
121377          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
121378          * - rowIdx - The row index that is being edited
121379          * - colIdx - The column index that is being edited
121380          * - cancel - Set this to true to cancel the edit or return false from your handler.
121381          */
121382         this.callParent(arguments);
121383         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
121384             return editor.editorId;
121385         });
121386         this.editTask = Ext.create('Ext.util.DelayedTask');
121387     },
121388     
121389     onReconfigure: function(){
121390         this.editors.clear();
121391         this.callParent();    
121392     },
121393
121394     /**
121395      * @private
121396      * AbstractComponent calls destroy on all its plugins at destroy time.
121397      */
121398     destroy: function() {
121399         var me = this;
121400         me.editTask.cancel();
121401         me.editors.each(Ext.destroy, Ext);
121402         me.editors.clear();
121403         me.callParent(arguments);
121404     },
121405     
121406     onBodyScroll: function() {
121407         var ed = this.getActiveEditor();
121408         if (ed && ed.field) {
121409             if (ed.field.triggerBlur) {
121410                 ed.field.triggerBlur();
121411             } else {
121412                 ed.field.blur();
121413             }
121414         }
121415     },
121416
121417     // private
121418     // Template method called from base class's initEvents
121419     initCancelTriggers: function() {
121420         var me   = this,
121421             grid = me.grid,
121422             view = grid.view;
121423             
121424         view.addElListener('mousewheel', me.cancelEdit, me);
121425         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
121426         me.mon(grid, {
121427             columnresize: me.cancelEdit,
121428             columnmove: me.cancelEdit,
121429             scope: me
121430         });
121431     },
121432
121433     /**
121434      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121435      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
121436      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
121437      */
121438     startEdit: function(record, columnHeader) {
121439         var me = this,
121440             value = record.get(columnHeader.dataIndex),
121441             context = me.getEditingContext(record, columnHeader),
121442             ed;
121443
121444         record = context.record;
121445         columnHeader = context.column;
121446
121447         // Complete the edit now, before getting the editor's target
121448         // cell DOM element. Completing the edit causes a view refresh.
121449         me.completeEdit();
121450
121451         context.originalValue = context.value = value;
121452         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
121453             return false;
121454         }
121455         
121456         // See if the field is editable for the requested record
121457         if (columnHeader && !columnHeader.getEditor(record)) {
121458             return false;
121459         }
121460         
121461         ed = me.getEditor(record, columnHeader);
121462         if (ed) {
121463             me.context = context;
121464             me.setActiveEditor(ed);
121465             me.setActiveRecord(record);
121466             me.setActiveColumn(columnHeader);
121467
121468             // Defer, so we have some time between view scroll to sync up the editor
121469             me.editTask.delay(15, ed.startEdit, ed, [me.getCell(record, columnHeader), value]);
121470         } else {
121471             // BrowserBug: WebKit & IE refuse to focus the element, rather
121472             // it will focus it and then immediately focus the body. This
121473             // temporary hack works for Webkit and IE6. IE7 and 8 are still
121474             // broken
121475             me.grid.getView().getEl(columnHeader).focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
121476         }
121477     },
121478
121479     completeEdit: function() {
121480         var activeEd = this.getActiveEditor();
121481         if (activeEd) {
121482             activeEd.completeEdit();
121483         }
121484     },
121485
121486     // internal getters/setters
121487     setActiveEditor: function(ed) {
121488         this.activeEditor = ed;
121489     },
121490
121491     getActiveEditor: function() {
121492         return this.activeEditor;
121493     },
121494
121495     setActiveColumn: function(column) {
121496         this.activeColumn = column;
121497     },
121498
121499     getActiveColumn: function() {
121500         return this.activeColumn;
121501     },
121502
121503     setActiveRecord: function(record) {
121504         this.activeRecord = record;
121505     },
121506
121507     getActiveRecord: function() {
121508         return this.activeRecord;
121509     },
121510
121511     getEditor: function(record, column) {
121512         var me = this,
121513             editors = me.editors,
121514             editorId = column.getItemId(),
121515             editor = editors.getByKey(editorId);
121516
121517         if (editor) {
121518             return editor;
121519         } else {
121520             editor = column.getEditor(record);
121521             if (!editor) {
121522                 return false;
121523             }
121524
121525             // Allow them to specify a CellEditor in the Column
121526             if (!(editor instanceof Ext.grid.CellEditor)) {
121527                 editor = Ext.create('Ext.grid.CellEditor', {
121528                     editorId: editorId,
121529                     field: editor
121530                 });
121531             }
121532             editor.parentEl = me.grid.getEditorParent();
121533             // editor.parentEl should be set here.
121534             editor.on({
121535                 scope: me,
121536                 specialkey: me.onSpecialKey,
121537                 complete: me.onEditComplete,
121538                 canceledit: me.cancelEdit
121539             });
121540             editors.add(editor);
121541             return editor;
121542         }
121543     },
121544     
121545     // inherit docs
121546     setColumnField: function(column, field) {
121547         var ed = this.editors.getByKey(column.getItemId());
121548         Ext.destroy(ed, column.field);
121549         this.editors.removeAtKey(column.getItemId());
121550         this.callParent(arguments);
121551     },
121552
121553     /**
121554      * Gets the cell (td) for a particular record and column.
121555      * @param {Ext.data.Model} record
121556      * @param {Ext.grid.column.Column} column
121557      * @private
121558      */
121559     getCell: function(record, column) {
121560         return this.grid.getView().getCell(record, column);
121561     },
121562
121563     onSpecialKey: function(ed, field, e) {
121564         var grid = this.grid,
121565             sm;
121566         if (e.getKey() === e.TAB) {
121567             e.stopEvent();
121568             sm = grid.getSelectionModel();
121569             if (sm.onEditorTab) {
121570                 sm.onEditorTab(this, e);
121571             }
121572         }
121573     },
121574
121575     onEditComplete : function(ed, value, startValue) {
121576         var me = this,
121577             grid = me.grid,
121578             sm = grid.getSelectionModel(),
121579             activeColumn = me.getActiveColumn(),
121580             dataIndex;
121581
121582         if (activeColumn) {
121583             dataIndex = activeColumn.dataIndex;
121584
121585             me.setActiveEditor(null);
121586             me.setActiveColumn(null);
121587             me.setActiveRecord(null);
121588             delete sm.wasEditing;
121589     
121590             if (!me.validateEdit()) {
121591                 return;
121592             }
121593             // Only update the record if the new value is different than the
121594             // startValue, when the view refreshes its el will gain focus
121595             if (value !== startValue) {
121596                 me.context.record.set(dataIndex, value);
121597             // Restore focus back to the view's element.
121598             } else {
121599                 grid.getView().getEl(activeColumn).focus();
121600             }
121601             me.context.value = value;
121602             me.fireEvent('edit', me, me.context);
121603         }
121604     },
121605
121606     /**
121607      * Cancels any active editing.
121608      */
121609     cancelEdit: function() {
121610         var me = this,
121611             activeEd = me.getActiveEditor(),
121612             viewEl = me.grid.getView().getEl(me.getActiveColumn());
121613
121614         me.setActiveEditor(null);
121615         me.setActiveColumn(null);
121616         me.setActiveRecord(null);
121617         if (activeEd) {
121618             activeEd.cancelEdit();
121619             viewEl.focus();
121620         }
121621     },
121622
121623     /**
121624      * Starts editing by position (row/column)
121625      * @param {Object} position A position with keys of row and column.
121626      */
121627     startEditByPosition: function(position) {
121628         var me = this,
121629             grid = me.grid,
121630             sm = grid.getSelectionModel(),
121631             editRecord = grid.store.getAt(position.row),
121632             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
121633
121634         if (sm.selectByPosition) {
121635             sm.selectByPosition(position);
121636         }
121637         me.startEdit(editRecord, editColumnHeader);
121638     }
121639 });
121640 /**
121641  * This plugin provides drag and/or drop functionality for a GridView.
121642  *
121643  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
121644  * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
121645  * methods with the following properties:
121646  *
121647  * - `copy` : Boolean
121648  *
121649  *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
121650  *   the control key was pressed when the drag operation was begun.
121651  *
121652  * - `view` : GridView
121653  *
121654  *   The source GridView from which the drag originated.
121655  *
121656  * - `ddel` : HtmlElement
121657  *
121658  *   The drag proxy element which moves with the mouse
121659  *
121660  * - `item` : HtmlElement
121661  *
121662  *   The GridView node upon which the mousedown event was registered.
121663  *
121664  * - `records` : Array
121665  *
121666  *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121667  *
121668  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
121669  * members of the same ddGroup which processes such data objects.
121670  *
121671  * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
121672  * beforedrop}` and `{@link #drop drop}`
121673  *
121674  *     @example
121675  *     Ext.create('Ext.data.Store', {
121676  *         storeId:'simpsonsStore',
121677  *         fields:['name'],
121678  *         data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
121679  *         proxy: {
121680  *             type: 'memory',
121681  *             reader: 'array'
121682  *         }
121683  *     });
121684  *
121685  *     Ext.create('Ext.grid.Panel', {
121686  *         store: 'simpsonsStore',
121687  *         columns: [
121688  *             {header: 'Name',  dataIndex: 'name', flex: true}
121689  *         ],
121690  *         viewConfig: {
121691  *             plugins: {
121692  *                 ptype: 'gridviewdragdrop',
121693  *                 dragText: 'Drag and drop to reorganize'
121694  *             }
121695  *         },
121696  *         height: 200,
121697  *         width: 400,
121698  *         renderTo: Ext.getBody()
121699  *     });
121700  */
121701 Ext.define('Ext.grid.plugin.DragDrop', {
121702     extend: 'Ext.AbstractPlugin',
121703     alias: 'plugin.gridviewdragdrop',
121704
121705     uses: [
121706         'Ext.view.DragZone',
121707         'Ext.grid.ViewDropZone'
121708     ],
121709
121710     /**
121711      * @event beforedrop
121712      * **This event is fired through the GridView. Add listeners to the GridView object**
121713      *
121714      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
121715      *
121716      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
121717      *
121718      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
121719      * back to the point from which the drag began.
121720      *
121721      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
121722      * was valid, and that the repair operation should not take place.
121723      *
121724      * Any other return value continues with the data transfer operation.
121725      *
121726      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
121727      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
121728      *
121729      * - copy : Boolean
121730      *
121731      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
121732      *   the control key was pressed when the drag operation was begun
121733      *
121734      * - view : GridView
121735      *
121736      *   The source GridView from which the drag originated.
121737      *
121738      * - ddel : HtmlElement
121739      *
121740      *   The drag proxy element which moves with the mouse
121741      *
121742      * - item : HtmlElement
121743      *
121744      *   The GridView node upon which the mousedown event was registered.
121745      *
121746      * - records : Array
121747      *
121748      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121749      *
121750      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
121751      *
121752      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
121753      * of the node.
121754      *
121755      * @param {Function} dropFunction
121756      *
121757      * A function to call to complete the data transfer operation and either move or copy Model instances from the
121758      * source View's Store to the destination View's Store.
121759      *
121760      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
121761      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
121762      *
121763      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
121764      */
121765
121766     /**
121767      * @event drop
121768      * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
121769      * has been completed and the data has been moved or copied.
121770      *
121771      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
121772      *
121773      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
121774      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
121775      *
121776      * - copy : Boolean
121777      *
121778      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
121779      *   the control key was pressed when the drag operation was begun
121780      *
121781      * - view : GridView
121782      *
121783      *   The source GridView from which the drag originated.
121784      *
121785      * - ddel : HtmlElement
121786      *
121787      *   The drag proxy element which moves with the mouse
121788      *
121789      * - item : HtmlElement
121790      *
121791      *   The GridView node upon which the mousedown event was registered.
121792      *
121793      * - records : Array
121794      *
121795      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
121796      *
121797      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
121798      *
121799      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
121800      * of the node.
121801      */
121802
121803     dragText : '{0} selected row{1}',
121804
121805     /**
121806      * @cfg {String} ddGroup
121807      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
121808      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
121809      */
121810     ddGroup : "GridDD",
121811
121812     /**
121813      * @cfg {String} dragGroup
121814      * The ddGroup to which the DragZone will belong.
121815      *
121816      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
121817      * Drag/DropZones which are members of the same ddGroup.
121818      */
121819
121820     /**
121821      * @cfg {String} dropGroup
121822      * The ddGroup to which the DropZone will belong.
121823      *
121824      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
121825      * Drag/DropZones which are members of the same ddGroup.
121826      */
121827
121828     /**
121829      * @cfg {Boolean} enableDrop
121830      * False to disallow the View from accepting drop gestures.
121831      */
121832     enableDrop: true,
121833
121834     /**
121835      * @cfg {Boolean} enableDrag
121836      * False to disallow dragging items from the View.
121837      */
121838     enableDrag: true,
121839
121840     init : function(view) {
121841         view.on('render', this.onViewRender, this, {single: true});
121842     },
121843
121844     /**
121845      * @private
121846      * AbstractComponent calls destroy on all its plugins at destroy time.
121847      */
121848     destroy: function() {
121849         Ext.destroy(this.dragZone, this.dropZone);
121850     },
121851
121852     enable: function() {
121853         var me = this;
121854         if (me.dragZone) {
121855             me.dragZone.unlock();
121856         }
121857         if (me.dropZone) {
121858             me.dropZone.unlock();
121859         }
121860         me.callParent();
121861     },
121862
121863     disable: function() {
121864         var me = this;
121865         if (me.dragZone) {
121866             me.dragZone.lock();
121867         }
121868         if (me.dropZone) {
121869             me.dropZone.lock();
121870         }
121871         me.callParent();
121872     },
121873
121874     onViewRender : function(view) {
121875         var me = this;
121876
121877         if (me.enableDrag) {
121878             me.dragZone = Ext.create('Ext.view.DragZone', {
121879                 view: view,
121880                 ddGroup: me.dragGroup || me.ddGroup,
121881                 dragText: me.dragText
121882             });
121883         }
121884
121885         if (me.enableDrop) {
121886             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
121887                 view: view,
121888                 ddGroup: me.dropGroup || me.ddGroup
121889             });
121890         }
121891     }
121892 });
121893 /**
121894  * @class Ext.grid.plugin.HeaderReorderer
121895  * @extends Ext.util.Observable
121896  * @private
121897  */
121898 Ext.define('Ext.grid.plugin.HeaderReorderer', {
121899     extend: 'Ext.util.Observable',
121900     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
121901     alias: 'plugin.gridheaderreorderer',
121902
121903     init: function(headerCt) {
121904         this.headerCt = headerCt;
121905         headerCt.on('render', this.onHeaderCtRender, this);
121906     },
121907
121908     /**
121909      * @private
121910      * AbstractComponent calls destroy on all its plugins at destroy time.
121911      */
121912     destroy: function() {
121913         Ext.destroy(this.dragZone, this.dropZone);
121914     },
121915
121916     onHeaderCtRender: function() {
121917         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
121918         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
121919         if (this.disabled) {
121920             this.dragZone.disable();
121921         }
121922     },
121923     
121924     enable: function() {
121925         this.disabled = false;
121926         if (this.dragZone) {
121927             this.dragZone.enable();
121928         }
121929     },
121930     
121931     disable: function() {
121932         this.disabled = true;
121933         if (this.dragZone) {
121934             this.dragZone.disable();
121935         }
121936     }
121937 });
121938 /**
121939  * @class Ext.grid.plugin.HeaderResizer
121940  * @extends Ext.util.Observable
121941  *
121942  * Plugin to add header resizing functionality to a HeaderContainer.
121943  * Always resizing header to the left of the splitter you are resizing.
121944  */
121945 Ext.define('Ext.grid.plugin.HeaderResizer', {
121946     extend: 'Ext.util.Observable',
121947     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
121948     alias: 'plugin.gridheaderresizer',
121949
121950     disabled: false,
121951
121952     /**
121953      * @cfg {Boolean} dynamic
121954      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
121955      */
121956     configs: {
121957         dynamic: true
121958     },
121959
121960     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
121961
121962     minColWidth: 40,
121963     maxColWidth: 1000,
121964     wResizeCursor: 'col-resize',
121965     eResizeCursor: 'col-resize',
121966     // not using w and e resize bc we are only ever resizing one
121967     // column
121968     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
121969     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
121970
121971     init: function(headerCt) {
121972         this.headerCt = headerCt;
121973         headerCt.on('render', this.afterHeaderRender, this, {single: true});
121974     },
121975
121976     /**
121977      * @private
121978      * AbstractComponent calls destroy on all its plugins at destroy time.
121979      */
121980     destroy: function() {
121981         if (this.tracker) {
121982             this.tracker.destroy();
121983         }
121984     },
121985
121986     afterHeaderRender: function() {
121987         var headerCt = this.headerCt,
121988             el = headerCt.el;
121989
121990         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
121991
121992         this.tracker = Ext.create('Ext.dd.DragTracker', {
121993             disabled: this.disabled,
121994             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
121995             onStart: Ext.Function.bind(this.onStart, this),
121996             onDrag: Ext.Function.bind(this.onDrag, this),
121997             onEnd: Ext.Function.bind(this.onEnd, this),
121998             tolerance: 3,
121999             autoStart: 300,
122000             el: el
122001         });
122002     },
122003
122004     // As we mouse over individual headers, change the cursor to indicate
122005     // that resizing is available, and cache the resize target header for use
122006     // if/when they mousedown.
122007     onHeaderCtMouseMove: function(e, t) {
122008         if (this.headerCt.dragging) {
122009             if (this.activeHd) {
122010                 this.activeHd.el.dom.style.cursor = '';
122011                 delete this.activeHd;
122012             }
122013         } else {
122014             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
122015                 overHeader, resizeHeader;
122016
122017             if (headerEl){
122018                 overHeader = Ext.getCmp(headerEl.id);
122019
122020                 // On left edge, go back to the previous non-hidden header.
122021                 if (overHeader.isOnLeftEdge(e)) {
122022                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
122023
122024                 }
122025                 // Else, if on the right edge, we're resizing the column we are over
122026                 else if (overHeader.isOnRightEdge(e)) {
122027                     resizeHeader = overHeader;
122028                 }
122029                 // Between the edges: we are not resizing
122030                 else {
122031                     resizeHeader = null;
122032                 }
122033
122034                 // We *are* resizing
122035                 if (resizeHeader) {
122036                     // If we're attempting to resize a group header, that cannot be resized,
122037                     // so find its last visible leaf header; Group headers are sized
122038                     // by the size of their child headers.
122039                     if (resizeHeader.isGroupHeader) {
122040                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
122041                     }
122042
122043                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
122044                     // check whether the resizablwe property is set to false.
122045                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
122046                         this.activeHd = resizeHeader;
122047                         overHeader.el.dom.style.cursor = this.eResizeCursor;
122048                     }
122049                 // reset
122050                 } else {
122051                     overHeader.el.dom.style.cursor = '';
122052                     delete this.activeHd;
122053                 }
122054             }
122055         }
122056     },
122057
122058     // only start when there is an activeHd
122059     onBeforeStart : function(e){
122060         var t = e.getTarget();
122061         // cache the activeHd because it will be cleared.
122062         this.dragHd = this.activeHd;
122063
122064         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
122065             //this.headerCt.dragging = true;
122066             this.tracker.constrainTo = this.getConstrainRegion();
122067             return true;
122068         } else {
122069             this.headerCt.dragging = false;
122070             return false;
122071         }
122072     },
122073
122074     // get the region to constrain to, takes into account max and min col widths
122075     getConstrainRegion: function() {
122076         var dragHdEl = this.dragHd.el,
122077             region   = Ext.util.Region.getRegion(dragHdEl);
122078
122079         return region.adjust(
122080             0,
122081             this.maxColWidth - dragHdEl.getWidth(),
122082             0,
122083             this.minColWidth
122084         );
122085     },
122086
122087     // initialize the left and right hand side markers around
122088     // the header that we are resizing
122089     onStart: function(e){
122090         var me       = this,
122091             dragHd   = me.dragHd,
122092             dragHdEl = dragHd.el,
122093             width    = dragHdEl.getWidth(),
122094             headerCt = me.headerCt,
122095             t        = e.getTarget();
122096
122097         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
122098             headerCt.dragging = true;
122099         }
122100
122101         me.origWidth = width;
122102
122103         // setup marker proxies
122104         if (!me.dynamic) {
122105             var xy           = dragHdEl.getXY(),
122106                 gridSection  = headerCt.up('[scrollerOwner]'),
122107                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
122108                 firstSection = dragHct.up(),
122109                 lhsMarker    = gridSection.getLhsMarker(),
122110                 rhsMarker    = gridSection.getRhsMarker(),
122111                 el           = rhsMarker.parent(),
122112                 offsetLeft   = el.getLeft(true),
122113                 offsetTop    = el.getTop(true),
122114                 topLeft      = el.translatePoints(xy),
122115                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
122116                 top = topLeft.top - offsetTop;
122117
122118             lhsMarker.setTop(top);
122119             rhsMarker.setTop(top);
122120             lhsMarker.setHeight(markerHeight);
122121             rhsMarker.setHeight(markerHeight);
122122             lhsMarker.setLeft(topLeft.left - offsetLeft);
122123             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
122124         }
122125     },
122126
122127     // synchronize the rhsMarker with the mouse movement
122128     onDrag: function(e){
122129         if (!this.dynamic) {
122130             var xy          = this.tracker.getXY('point'),
122131                 gridSection = this.headerCt.up('[scrollerOwner]'),
122132                 rhsMarker   = gridSection.getRhsMarker(),
122133                 el          = rhsMarker.parent(),
122134                 topLeft     = el.translatePoints(xy),
122135                 offsetLeft  = el.getLeft(true);
122136
122137             rhsMarker.setLeft(topLeft.left - offsetLeft);
122138         // Resize as user interacts
122139         } else {
122140             this.doResize();
122141         }
122142     },
122143
122144     onEnd: function(e){
122145         this.headerCt.dragging = false;
122146         if (this.dragHd) {
122147             if (!this.dynamic) {
122148                 var dragHd      = this.dragHd,
122149                     gridSection = this.headerCt.up('[scrollerOwner]'),
122150                     lhsMarker   = gridSection.getLhsMarker(),
122151                     rhsMarker   = gridSection.getRhsMarker(),
122152                     currWidth   = dragHd.getWidth(),
122153                     offset      = this.tracker.getOffset('point'),
122154                     offscreen   = -9999;
122155
122156                 // hide markers
122157                 lhsMarker.setLeft(offscreen);
122158                 rhsMarker.setLeft(offscreen);
122159             }
122160             this.doResize();
122161         }
122162     },
122163
122164     doResize: function() {
122165         if (this.dragHd) {
122166             var dragHd = this.dragHd,
122167                 nextHd,
122168                 offset = this.tracker.getOffset('point');
122169
122170             // resize the dragHd
122171             if (dragHd.flex) {
122172                 delete dragHd.flex;
122173             }
122174
122175             this.headerCt.suspendLayout = true;
122176             dragHd.setWidth(this.origWidth + offset[0], false);
122177
122178             // In the case of forceFit, change the following Header width.
122179             // Then apply the two width changes by laying out the owning HeaderContainer
122180             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
122181             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
122182             if (this.headerCt.forceFit) {
122183                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
122184                 if (nextHd) {
122185                     delete nextHd.flex;
122186                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
122187                 }
122188             }
122189             this.headerCt.suspendLayout = false;
122190             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
122191         }
122192     },
122193
122194     disable: function() {
122195         this.disabled = true;
122196         if (this.tracker) {
122197             this.tracker.disable();
122198         }
122199     },
122200
122201     enable: function() {
122202         this.disabled = false;
122203         if (this.tracker) {
122204             this.tracker.enable();
122205         }
122206     }
122207 });
122208 /**
122209  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
122210  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
122211  * for editing. There is a button to save or cancel all changes for the edit.
122212  *
122213  * The field that will be used for the editor is defined at the
122214  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
122215  * If an editor is not specified for a particular column then that column won't be editable and the value of
122216  * the column will be displayed.
122217  *
122218  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
122219  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
122220  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
122221  *
122222  *     @example
122223  *     Ext.create('Ext.data.Store', {
122224  *         storeId:'simpsonsStore',
122225  *         fields:['name', 'email', 'phone'],
122226  *         data: [
122227  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
122228  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
122229  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
122230  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
122231  *         ]
122232  *     });
122233  *
122234  *     Ext.create('Ext.grid.Panel', {
122235  *         title: 'Simpsons',
122236  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
122237  *         columns: [
122238  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
122239  *             {header: 'Email', dataIndex: 'email', flex:1,
122240  *                 editor: {
122241  *                     xtype: 'textfield',
122242  *                     allowBlank: false
122243  *                 }
122244  *             },
122245  *             {header: 'Phone', dataIndex: 'phone'}
122246  *         ],
122247  *         selType: 'rowmodel',
122248  *         plugins: [
122249  *             Ext.create('Ext.grid.plugin.RowEditing', {
122250  *                 clicksToEdit: 1
122251  *             })
122252  *         ],
122253  *         height: 200,
122254  *         width: 400,
122255  *         renderTo: Ext.getBody()
122256  *     });
122257  */
122258 Ext.define('Ext.grid.plugin.RowEditing', {
122259     extend: 'Ext.grid.plugin.Editing',
122260     alias: 'plugin.rowediting',
122261
122262     requires: [
122263         'Ext.grid.RowEditor'
122264     ],
122265
122266     editStyle: 'row',
122267
122268     /**
122269      * @cfg {Boolean} autoCancel
122270      * True to automatically cancel any pending changes when the row editor begins editing a new row.
122271      * False to force the user to explicitly cancel the pending changes. Defaults to true.
122272      */
122273     autoCancel: true,
122274
122275     /**
122276      * @cfg {Number} clicksToMoveEditor
122277      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
122278      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
122279      */
122280
122281     /**
122282      * @cfg {Boolean} errorSummary
122283      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
122284      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
122285      */
122286     errorSummary: true,
122287
122288     /**
122289      * @event beforeedit
122290      * Fires before row editing is triggered.
122291      *
122292      * @param {Ext.grid.plugin.Editing} editor
122293      * @param {Object} e An edit event with the following properties:
122294      *
122295      * - grid - The grid this editor is on
122296      * - view - The grid view
122297      * - store - The grid store
122298      * - record - The record being edited
122299      * - row - The grid table row
122300      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
122301      * - rowIdx - The row index that is being edited
122302      * - colIdx - The column index that initiated the edit
122303      * - cancel - Set this to true to cancel the edit or return false from your handler.
122304      */
122305     
122306     /**
122307      * @event canceledit
122308      * Fires when the user has started editing a row but then cancelled the edit
122309      * @param {Object} grid The grid
122310      */
122311     
122312     /**
122313      * @event edit
122314      * Fires after a row is edited. Usage example:
122315      *
122316      *     grid.on('edit', function(editor, e) {
122317      *         // commit the changes right after editing finished
122318      *         e.record.commit();
122319      *     };
122320      *
122321      * @param {Ext.grid.plugin.Editing} editor
122322      * @param {Object} e An edit event with the following properties:
122323      *
122324      * - grid - The grid this editor is on
122325      * - view - The grid view
122326      * - store - The grid store
122327      * - record - The record being edited
122328      * - row - The grid table row
122329      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
122330      * - rowIdx - The row index that is being edited
122331      * - colIdx - The column index that initiated the edit
122332      */
122333     /**
122334      * @event validateedit
122335      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
122336      * edit event object has the following properties
122337      *
122338      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
122339      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
122340      * and then setting the field's new value in the Record directly:
122341      *
122342      *     grid.on('validateedit', function(editor, e) {
122343      *       var myTargetRow = 6;
122344      *
122345      *       if (e.rowIdx == myTargetRow) {
122346      *         e.cancel = true;
122347      *         e.record.data[e.field] = e.value;
122348      *       }
122349      *     });
122350      *
122351      * @param {Ext.grid.plugin.Editing} editor
122352      * @param {Object} e An edit event with the following properties:
122353      *
122354      * - grid - The grid this editor is on
122355      * - view - The grid view
122356      * - store - The grid store
122357      * - record - The record being edited
122358      * - row - The grid table row
122359      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
122360      * - rowIdx - The row index that is being edited
122361      * - colIdx - The column index that initiated the edit
122362      * - cancel - Set this to true to cancel the edit or return false from your handler.
122363      */
122364
122365     constructor: function() {
122366         var me = this;
122367         me.callParent(arguments);
122368
122369         if (!me.clicksToMoveEditor) {
122370             me.clicksToMoveEditor = me.clicksToEdit;
122371         }
122372
122373         me.autoCancel = !!me.autoCancel;
122374     },
122375
122376     /**
122377      * @private
122378      * AbstractComponent calls destroy on all its plugins at destroy time.
122379      */
122380     destroy: function() {
122381         var me = this;
122382         Ext.destroy(me.editor);
122383         me.callParent(arguments);
122384     },
122385
122386     /**
122387      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
122388      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
122389      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
122390      */
122391     startEdit: function(record, columnHeader) {
122392         var me = this,
122393             editor = me.getEditor();
122394
122395         if (me.callParent(arguments) === false) {
122396             return false;
122397         }
122398
122399         // Fire off our editor
122400         if (editor.beforeEdit() !== false) {
122401             editor.startEdit(me.context.record, me.context.column);
122402         }
122403     },
122404
122405     // private
122406     cancelEdit: function() {
122407         var me = this;
122408
122409         if (me.editing) {
122410             me.getEditor().cancelEdit();
122411             me.callParent(arguments);
122412             
122413             me.fireEvent('canceledit', me.context);
122414         }
122415     },
122416
122417     // private
122418     completeEdit: function() {
122419         var me = this;
122420
122421         if (me.editing && me.validateEdit()) {
122422             me.editing = false;
122423             me.fireEvent('edit', me.context);
122424         }
122425     },
122426
122427     // private
122428     validateEdit: function() {
122429         var me             = this,
122430             editor         = me.editor,
122431             context        = me.context,
122432             record         = context.record,
122433             newValues      = {},
122434             originalValues = {},
122435             name;
122436
122437         editor.items.each(function(item) {
122438             name = item.name;
122439
122440             newValues[name]      = item.getValue();
122441             originalValues[name] = record.get(name);
122442         });
122443
122444         Ext.apply(context, {
122445             newValues      : newValues,
122446             originalValues : originalValues
122447         });
122448
122449         return me.callParent(arguments) && me.getEditor().completeEdit();
122450     },
122451
122452     // private
122453     getEditor: function() {
122454         var me = this;
122455
122456         if (!me.editor) {
122457             me.editor = me.initEditor();
122458         }
122459         return me.editor;
122460     },
122461
122462     // private
122463     initEditor: function() {
122464         var me = this,
122465             grid = me.grid,
122466             view = me.view,
122467             headerCt = grid.headerCt;
122468
122469         return Ext.create('Ext.grid.RowEditor', {
122470             autoCancel: me.autoCancel,
122471             errorSummary: me.errorSummary,
122472             fields: headerCt.getGridColumns(),
122473             hidden: true,
122474
122475             // keep a reference..
122476             editingPlugin: me,
122477             renderTo: view.el
122478         });
122479     },
122480
122481     // private
122482     initEditTriggers: function() {
122483         var me = this,
122484             grid = me.grid,
122485             view = me.view,
122486             headerCt = grid.headerCt,
122487             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
122488
122489         me.callParent(arguments);
122490
122491         if (me.clicksToMoveEditor !== me.clicksToEdit) {
122492             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
122493         }
122494
122495         view.on('render', function() {
122496             // Column events
122497             me.mon(headerCt, {
122498                 add: me.onColumnAdd,
122499                 remove: me.onColumnRemove,
122500                 columnresize: me.onColumnResize,
122501                 columnhide: me.onColumnHide,
122502                 columnshow: me.onColumnShow,
122503                 columnmove: me.onColumnMove,
122504                 scope: me
122505             });
122506         }, me, { single: true });
122507     },
122508
122509     startEditByClick: function() {
122510         var me = this;
122511         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
122512             me.callParent(arguments);
122513         }
122514     },
122515
122516     moveEditorByClick: function() {
122517         var me = this;
122518         if (me.editing) {
122519             me.superclass.startEditByClick.apply(me, arguments);
122520         }
122521     },
122522
122523     // private
122524     onColumnAdd: function(ct, column) {
122525         if (column.isHeader) {
122526             var me = this,
122527                 editor;
122528
122529             me.initFieldAccessors(column);
122530             editor = me.getEditor();
122531
122532             if (editor && editor.onColumnAdd) {
122533                 editor.onColumnAdd(column);
122534             }
122535         }
122536     },
122537
122538     // private
122539     onColumnRemove: function(ct, column) {
122540         if (column.isHeader) {
122541             var me = this,
122542                 editor = me.getEditor();
122543
122544             if (editor && editor.onColumnRemove) {
122545                 editor.onColumnRemove(column);
122546             }
122547             me.removeFieldAccessors(column);
122548         }
122549     },
122550
122551     // private
122552     onColumnResize: function(ct, column, width) {
122553         if (column.isHeader) {
122554             var me = this,
122555                 editor = me.getEditor();
122556
122557             if (editor && editor.onColumnResize) {
122558                 editor.onColumnResize(column, width);
122559             }
122560         }
122561     },
122562
122563     // private
122564     onColumnHide: function(ct, column) {
122565         // no isHeader check here since its already a columnhide event.
122566         var me = this,
122567             editor = me.getEditor();
122568
122569         if (editor && editor.onColumnHide) {
122570             editor.onColumnHide(column);
122571         }
122572     },
122573
122574     // private
122575     onColumnShow: function(ct, column) {
122576         // no isHeader check here since its already a columnshow event.
122577         var me = this,
122578             editor = me.getEditor();
122579
122580         if (editor && editor.onColumnShow) {
122581             editor.onColumnShow(column);
122582         }
122583     },
122584
122585     // private
122586     onColumnMove: function(ct, column, fromIdx, toIdx) {
122587         // no isHeader check here since its already a columnmove event.
122588         var me = this,
122589             editor = me.getEditor();
122590
122591         if (editor && editor.onColumnMove) {
122592             editor.onColumnMove(column, fromIdx, toIdx);
122593         }
122594     },
122595
122596     // private
122597     setColumnField: function(column, field) {
122598         var me = this;
122599         me.callParent(arguments);
122600         me.getEditor().setField(column.field, column);
122601     }
122602 });
122603
122604 /**
122605  * @class Ext.grid.property.Grid
122606  * @extends Ext.grid.Panel
122607  *
122608  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
122609  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
122610  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
122611  *
122612  *     @example
122613  *     Ext.create('Ext.grid.property.Grid', {
122614  *         title: 'Properties Grid',
122615  *         width: 300,
122616  *         renderTo: Ext.getBody(),
122617  *         source: {
122618  *             "(name)": "My Object",
122619  *             "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
122620  *             "Available": false,
122621  *             "Version": .01,
122622  *             "Description": "A test object"
122623  *         }
122624  *     });
122625  */
122626 Ext.define('Ext.grid.property.Grid', {
122627
122628     extend: 'Ext.grid.Panel',
122629
122630     alias: 'widget.propertygrid',
122631
122632     alternateClassName: 'Ext.grid.PropertyGrid',
122633
122634     uses: [
122635        'Ext.grid.plugin.CellEditing',
122636        'Ext.grid.property.Store',
122637        'Ext.grid.property.HeaderContainer',
122638        'Ext.XTemplate',
122639        'Ext.grid.CellEditor',
122640        'Ext.form.field.Date',
122641        'Ext.form.field.Text',
122642        'Ext.form.field.Number'
122643     ],
122644
122645    /**
122646     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
122647     * If specified, the display name will be shown in the name column instead of the property name.
122648     */
122649
122650     /**
122651     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
122652     */
122653
122654     /**
122655     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
122656     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
122657     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
122658     * associated with a custom input control by specifying a custom editor.  The name of the editor
122659     * type should correspond with the name of the property that will use the editor.  Example usage:
122660     * <pre><code>
122661 var grid = new Ext.grid.property.Grid({
122662
122663     // Custom editors for certain property names
122664     customEditors: {
122665         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
122666     },
122667
122668     // Displayed name for property names in the source
122669     propertyNames: {
122670         evtStart: 'Start Time'
122671     },
122672
122673     // Data object containing properties to edit
122674     source: {
122675         evtStart: '10:00 AM'
122676     }
122677 });
122678 </code></pre>
122679     */
122680
122681     /**
122682     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
122683     */
122684
122685     /**
122686     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
122687     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
122688     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
122689     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
122690     * that it will render.  Example usage:
122691     * <pre><code>
122692 var grid = Ext.create('Ext.grid.property.Grid', {
122693     customRenderers: {
122694         Available: function(v){
122695             if (v) {
122696                 return '<span style="color: green;">Yes</span>';
122697             } else {
122698                 return '<span style="color: red;">No</span>';
122699             }
122700         }
122701     },
122702     source: {
122703         Available: true
122704     }
122705 });
122706 </code></pre>
122707     */
122708
122709     /**
122710      * @cfg {String} valueField
122711      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
122712      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
122713      */
122714     valueField: 'value',
122715
122716     /**
122717      * @cfg {String} nameField
122718      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
122719      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
122720      */
122721     nameField: 'name',
122722
122723     /**
122724      * @cfg {Number} nameColumnWidth
122725      * Optional. Specify the width for the name column. The value column will take any remaining space. Defaults to <tt>115</tt>.
122726      */
122727
122728     // private config overrides
122729     enableColumnMove: false,
122730     columnLines: true,
122731     stripeRows: false,
122732     trackMouseOver: false,
122733     clicksToEdit: 1,
122734     enableHdMenu: false,
122735
122736     // private
122737     initComponent : function(){
122738         var me = this;
122739
122740         me.addCls(Ext.baseCSSPrefix + 'property-grid');
122741         me.plugins = me.plugins || [];
122742
122743         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
122744         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
122745             clicksToEdit: me.clicksToEdit,
122746
122747             // Inject a startEdit which always edits the value column
122748             startEdit: function(record, column) {
122749                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
122750                 return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
122751             }
122752         }));
122753
122754         me.selModel = {
122755             selType: 'cellmodel',
122756             onCellSelect: function(position) {
122757                 if (position.column != 1) {
122758                     position.column = 1;
122759                 }
122760                 return this.self.prototype.onCellSelect.call(this, position);
122761             }
122762         };
122763         me.customRenderers = me.customRenderers || {};
122764         me.customEditors = me.customEditors || {};
122765
122766         // Create a property.Store from the source object unless configured with a store
122767         if (!me.store) {
122768             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
122769         }
122770
122771         me.store.sort('name', 'ASC');
122772         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
122773
122774         me.addEvents(
122775             /**
122776              * @event beforepropertychange
122777              * Fires before a property value changes.  Handlers can return false to cancel the property change
122778              * (this will internally call {@link Ext.data.Model#reject} on the property's record).
122779              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
122780              * as the {@link #source} config property).
122781              * @param {String} recordId The record's id in the data store
122782              * @param {Object} value The current edited property value
122783              * @param {Object} oldValue The original property value prior to editing
122784              */
122785             'beforepropertychange',
122786             /**
122787              * @event propertychange
122788              * Fires after a property value has changed.
122789              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
122790              * as the {@link #source} config property).
122791              * @param {String} recordId The record's id in the data store
122792              * @param {Object} value The current edited property value
122793              * @param {Object} oldValue The original property value prior to editing
122794              */
122795             'propertychange'
122796         );
122797         me.callParent();
122798
122799         // Inject a custom implementation of walkCells which only goes up or down
122800         me.getView().walkCells = this.walkCells;
122801
122802         // Set up our default editor set for the 4 atomic data types
122803         me.editors = {
122804             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
122805             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
122806             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
122807             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
122808                 editable: false,
122809                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
122810             })})
122811         };
122812
122813         // Track changes to the data so we can fire our events.
122814         me.store.on('update', me.onUpdate, me);
122815     },
122816
122817     // private
122818     onUpdate : function(store, record, operation) {
122819         var me = this,
122820             v, oldValue;
122821
122822         if (operation == Ext.data.Model.EDIT) {
122823             v = record.get(me.valueField);
122824             oldValue = record.modified.value;
122825             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
122826                 if (me.source) {
122827                     me.source[record.getId()] = v;
122828                 }
122829                 record.commit();
122830                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
122831             } else {
122832                 record.reject();
122833             }
122834         }
122835     },
122836
122837     // Custom implementation of walkCells which only goes up and down.
122838     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
122839         if (direction == 'left') {
122840             direction = 'up';
122841         } else if (direction == 'right') {
122842             direction = 'down';
122843         }
122844         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
122845         if (!pos.column) {
122846             pos.column = 1;
122847         }
122848         return pos;
122849     },
122850
122851     // private
122852     // returns the correct editor type for the property type, or a custom one keyed by the property name
122853     getCellEditor : function(record, column) {
122854         var me = this,
122855             propName = record.get(me.nameField),
122856             val = record.get(me.valueField),
122857             editor = me.customEditors[propName];
122858
122859         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
122860         // If it's not even a Field, just a config object, instantiate it before wrapping it.
122861         if (editor) {
122862             if (!(editor instanceof Ext.grid.CellEditor)) {
122863                 if (!(editor instanceof Ext.form.field.Base)) {
122864                     editor = Ext.ComponentManager.create(editor, 'textfield');
122865                 }
122866                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
122867             }
122868         } else if (Ext.isDate(val)) {
122869             editor = me.editors.date;
122870         } else if (Ext.isNumber(val)) {
122871             editor = me.editors.number;
122872         } else if (Ext.isBoolean(val)) {
122873             editor = me.editors['boolean'];
122874         } else {
122875             editor = me.editors.string;
122876         }
122877
122878         // Give the editor a unique ID because the CellEditing plugin caches them
122879         editor.editorId = propName;
122880         return editor;
122881     },
122882
122883     beforeDestroy: function() {
122884         var me = this;
122885         me.callParent();
122886         me.destroyEditors(me.editors);
122887         me.destroyEditors(me.customEditors);
122888         delete me.source;
122889     },
122890
122891     destroyEditors: function (editors) {
122892         for (var ed in editors) {
122893             if (editors.hasOwnProperty(ed)) {
122894                 Ext.destroy(editors[ed]);
122895             }
122896         }
122897     },
122898
122899     /**
122900      * Sets the source data object containing the property data.  The data object can contain one or more name/value
122901      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
122902      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
122903      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
122904      * existing data.  See also the {@link #source} config value.  Example usage:
122905      * <pre><code>
122906 grid.setSource({
122907     "(name)": "My Object",
122908     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
122909     "Available": false,  // boolean type
122910     "Version": .01,      // decimal type
122911     "Description": "A test object"
122912 });
122913 </code></pre>
122914      * @param {Object} source The data object
122915      */
122916     setSource: function(source) {
122917         this.source = source;
122918         this.propStore.setSource(source);
122919     },
122920
122921     /**
122922      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
122923      * format of the data object.
122924      * @return {Object} The data object
122925      */
122926     getSource: function() {
122927         return this.propStore.getSource();
122928     },
122929
122930     /**
122931      * Sets the value of a property.
122932      * @param {String} prop The name of the property to set
122933      * @param {Object} value The value to test
122934      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
122935      */
122936     setProperty: function(prop, value, create) {
122937         this.propStore.setValue(prop, value, create);
122938     },
122939
122940     /**
122941      * Removes a property from the grid.
122942      * @param {String} prop The name of the property to remove
122943      */
122944     removeProperty: function(prop) {
122945         this.propStore.remove(prop);
122946     }
122947
122948     /**
122949      * @cfg store
122950      * @hide
122951      */
122952     /**
122953      * @cfg colModel
122954      * @hide
122955      */
122956     /**
122957      * @cfg cm
122958      * @hide
122959      */
122960     /**
122961      * @cfg columns
122962      * @hide
122963      */
122964 });
122965 /**
122966  * @class Ext.grid.property.HeaderContainer
122967  * @extends Ext.grid.header.Container
122968  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
122969  */
122970 Ext.define('Ext.grid.property.HeaderContainer', {
122971
122972     extend: 'Ext.grid.header.Container',
122973
122974     alternateClassName: 'Ext.grid.PropertyColumnModel',
122975     
122976     nameWidth: 115,
122977
122978     // private - strings used for locale support
122979     nameText : 'Name',
122980     valueText : 'Value',
122981     dateFormat : 'm/j/Y',
122982     trueText: 'true',
122983     falseText: 'false',
122984
122985     // private
122986     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
122987
122988     /**
122989      * Creates new HeaderContainer.
122990      * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
122991      * @param {Object} source The source data config object
122992      */
122993     constructor : function(grid, store) {
122994         var me = this;
122995         
122996         me.grid = grid;
122997         me.store = store;
122998         me.callParent([{
122999             items: [{
123000                 header: me.nameText,
123001                 width: grid.nameColumnWidth || me.nameWidth,
123002                 sortable: true,
123003                 dataIndex: grid.nameField,
123004                 renderer: Ext.Function.bind(me.renderProp, me),
123005                 itemId: grid.nameField,
123006                 menuDisabled :true,
123007                 tdCls: me.nameColumnCls
123008             }, {
123009                 header: me.valueText,
123010                 renderer: Ext.Function.bind(me.renderCell, me),
123011                 getEditor: Ext.Function.bind(me.getCellEditor, me),
123012                 flex: 1,
123013                 fixed: true,
123014                 dataIndex: grid.valueField,
123015                 itemId: grid.valueField,
123016                 menuDisabled: true
123017             }]
123018         }]);
123019     },
123020     
123021     getCellEditor: function(record){
123022         return this.grid.getCellEditor(record, this);
123023     },
123024
123025     // private
123026     // Render a property name cell
123027     renderProp : function(v) {
123028         return this.getPropertyName(v);
123029     },
123030
123031     // private
123032     // Render a property value cell
123033     renderCell : function(val, meta, rec) {
123034         var me = this,
123035             renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
123036             result = val;
123037
123038         if (renderer) {
123039             return renderer.apply(me, arguments);
123040         }
123041         if (Ext.isDate(val)) {
123042             result = me.renderDate(val);
123043         } else if (Ext.isBoolean(val)) {
123044             result = me.renderBool(val);
123045         }
123046         return Ext.util.Format.htmlEncode(result);
123047     },
123048
123049     // private
123050     renderDate : Ext.util.Format.date,
123051
123052     // private
123053     renderBool : function(bVal) {
123054         return this[bVal ? 'trueText' : 'falseText'];
123055     },
123056
123057     // private
123058     // Renders custom property names instead of raw names if defined in the Grid
123059     getPropertyName : function(name) {
123060         var pn = this.grid.propertyNames;
123061         return pn && pn[name] ? pn[name] : name;
123062     }
123063 });
123064 /**
123065  * @class Ext.grid.property.Property
123066  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
123067  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
123068  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
123069  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
123070  * can also be created explicitly as shown below.  Example usage:
123071  * <pre><code>
123072 var rec = new Ext.grid.property.Property({
123073     name: 'birthday',
123074     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
123075 });
123076 // Add record to an already populated grid
123077 grid.store.addSorted(rec);
123078 </code></pre>
123079  * @constructor
123080  * @param {Object} config A data object in the format:<pre><code>
123081 {
123082     name: [name],
123083     value: [value]
123084 }</code></pre>
123085  * The specified value's type
123086  * will be read automatically by the grid to determine the type of editor to use when displaying it.
123087  */
123088 Ext.define('Ext.grid.property.Property', {
123089     extend: 'Ext.data.Model',
123090
123091     alternateClassName: 'Ext.PropGridProperty',
123092
123093     fields: [{
123094         name: 'name',
123095         type: 'string'
123096     }, {
123097         name: 'value'
123098     }],
123099     idProperty: 'name'
123100 });
123101 /**
123102  * @class Ext.grid.property.Store
123103  * @extends Ext.data.Store
123104  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
123105  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
123106  * used by the {@link Ext.data.Store} base class.
123107  */
123108 Ext.define('Ext.grid.property.Store', {
123109
123110     extend: 'Ext.data.Store',
123111
123112     alternateClassName: 'Ext.grid.PropertyStore',
123113
123114     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
123115
123116     /**
123117      * Creates new property store.
123118      * @param {Ext.grid.Panel} grid The grid this store will be bound to
123119      * @param {Object} source The source data config object
123120      */
123121     constructor : function(grid, source){
123122         var me = this;
123123         
123124         me.grid = grid;
123125         me.source = source;
123126         me.callParent([{
123127             data: source,
123128             model: Ext.grid.property.Property,
123129             proxy: me.getProxy()
123130         }]);
123131     },
123132
123133     // Return a singleton, customized Proxy object which configures itself with a custom Reader
123134     getProxy: function() {
123135         if (!this.proxy) {
123136             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
123137                 model: Ext.grid.property.Property,
123138                 reader: this.getReader()
123139             });
123140         }
123141         return this.proxy;
123142     },
123143
123144     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
123145     getReader: function() {
123146         if (!this.reader) {
123147             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
123148                 model: Ext.grid.property.Property,
123149
123150                 buildExtractors: Ext.emptyFn,
123151
123152                 read: function(dataObject) {
123153                     return this.readRecords(dataObject);
123154                 },
123155
123156                 readRecords: function(dataObject) {
123157                     var val,
123158                         propName,
123159                         result = {
123160                             records: [],
123161                             success: true
123162                         };
123163
123164                     for (propName in dataObject) {
123165                         if (dataObject.hasOwnProperty(propName)) {
123166                             val = dataObject[propName];
123167                             if (this.isEditableValue(val)) {
123168                                 result.records.push(new Ext.grid.property.Property({
123169                                     name: propName,
123170                                     value: val
123171                                 }, propName));
123172                             }
123173                         }
123174                     }
123175                     result.total = result.count = result.records.length;
123176                     return Ext.create('Ext.data.ResultSet', result);
123177                 },
123178
123179                 // private
123180                 isEditableValue: function(val){
123181                     return Ext.isPrimitive(val) || Ext.isDate(val);
123182                 }
123183             });
123184         }
123185         return this.reader;
123186     },
123187
123188     // protected - should only be called by the grid.  Use grid.setSource instead.
123189     setSource : function(dataObject) {
123190         var me = this;
123191
123192         me.source = dataObject;
123193         me.suspendEvents();
123194         me.removeAll();
123195         me.proxy.data = dataObject;
123196         me.load();
123197         me.resumeEvents();
123198         me.fireEvent('datachanged', me);
123199     },
123200
123201     // private
123202     getProperty : function(row) {
123203        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
123204     },
123205
123206     // private
123207     setValue : function(prop, value, create){
123208         var me = this,
123209             rec = me.getRec(prop);
123210             
123211         if (rec) {
123212             rec.set('value', value);
123213             me.source[prop] = value;
123214         } else if (create) {
123215             // only create if specified.
123216             me.source[prop] = value;
123217             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
123218             me.add(rec);
123219         }
123220     },
123221
123222     // private
123223     remove : function(prop) {
123224         var rec = this.getRec(prop);
123225         if (rec) {
123226             this.callParent([rec]);
123227             delete this.source[prop];
123228         }
123229     },
123230
123231     // private
123232     getRec : function(prop) {
123233         return this.getById(prop);
123234     },
123235
123236     // protected - should only be called by the grid.  Use grid.getSource instead.
123237     getSource : function() {
123238         return this.source;
123239     }
123240 });
123241 /**
123242  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
123243  * Component size.
123244  * @class Ext.layout.component.Body
123245  * @extends Ext.layout.component.Component
123246  * @private
123247  */
123248
123249 Ext.define('Ext.layout.component.Body', {
123250
123251     /* Begin Definitions */
123252
123253     alias: ['layout.body'],
123254
123255     extend: 'Ext.layout.component.Component',
123256
123257     uses: ['Ext.layout.container.Container'],
123258
123259     /* End Definitions */
123260
123261     type: 'body',
123262     
123263     onLayout: function(width, height) {
123264         var me = this,
123265             owner = me.owner;
123266
123267         // Size the Component's encapsulating element according to the dimensions
123268         me.setTargetSize(width, height);
123269
123270         // Size the Component's body element according to the content box of the encapsulating element
123271         me.setBodySize.apply(me, arguments);
123272
123273         // We need to bind to the owner whenever we do not have a user set height or width.
123274         if (owner && owner.layout && owner.layout.isLayout) {
123275             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
123276                 owner.layout.bindToOwnerCtComponent = true;
123277             }
123278             else {
123279                 owner.layout.bindToOwnerCtComponent = false;
123280             }
123281         }
123282         
123283         me.callParent(arguments);
123284     },
123285
123286     /**
123287      * @private
123288      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
123289      */
123290     setBodySize: function(width, height) {
123291         var me = this,
123292             owner = me.owner,
123293             frameSize = owner.frameSize,
123294             isNumber = Ext.isNumber;
123295
123296         if (isNumber(width)) {
123297             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
123298         }
123299         if (isNumber(height)) {
123300             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
123301         }
123302
123303         me.setElementSize(owner.body, width, height);
123304     }
123305 });
123306 /**
123307  * Component layout for Ext.form.FieldSet components
123308  * @class Ext.layout.component.FieldSet
123309  * @extends Ext.layout.component.Body
123310  * @private
123311  */
123312 Ext.define('Ext.layout.component.FieldSet', {
123313     extend: 'Ext.layout.component.Body',
123314     alias: ['layout.fieldset'],
123315
123316     type: 'fieldset',
123317
123318     doContainerLayout: function() {
123319         // Prevent layout/rendering of children if the fieldset is collapsed
123320         if (!this.owner.collapsed) {
123321             this.callParent();
123322         }
123323     }
123324 });
123325 /**
123326  * Component layout for tabs
123327  * @class Ext.layout.component.Tab
123328  * @extends Ext.layout.component.Button
123329  * @private
123330  */
123331 Ext.define('Ext.layout.component.Tab', {
123332
123333     alias: ['layout.tab'],
123334
123335     extend: 'Ext.layout.component.Button',
123336
123337     //type: 'button',
123338
123339     beforeLayout: function() {
123340         var me = this, dirty = me.lastClosable !== me.owner.closable;
123341
123342         if (dirty) {
123343             delete me.adjWidth;
123344         }
123345
123346         return this.callParent(arguments) || dirty;
123347     },
123348
123349     onLayout: function () {
123350         var me = this;
123351
123352         me.callParent(arguments);
123353
123354         me.lastClosable = me.owner.closable;
123355     }
123356 });
123357 /**
123358  * @private
123359  * @class Ext.layout.component.field.File
123360  * @extends Ext.layout.component.field.Field
123361  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
123362  * the file picker trigger button.
123363  * @private
123364  */
123365
123366 Ext.define('Ext.layout.component.field.File', {
123367     alias: ['layout.filefield'],
123368     extend: 'Ext.layout.component.field.Field',
123369
123370     type: 'filefield',
123371
123372     sizeBodyContents: function(width, height) {
123373         var me = this,
123374             owner = me.owner;
123375
123376         if (!owner.buttonOnly) {
123377             // Decrease the field's width by the width of the button and the configured buttonMargin.
123378             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
123379             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
123380         }
123381     }
123382 });
123383 /**
123384  * @class Ext.layout.component.field.Slider
123385  * @extends Ext.layout.component.field.Field
123386  * @private
123387  */
123388
123389 Ext.define('Ext.layout.component.field.Slider', {
123390
123391     /* Begin Definitions */
123392
123393     alias: ['layout.sliderfield'],
123394
123395     extend: 'Ext.layout.component.field.Field',
123396
123397     /* End Definitions */
123398
123399     type: 'sliderfield',
123400
123401     sizeBodyContents: function(width, height) {
123402         var owner = this.owner,
123403             thumbs = owner.thumbs,
123404             length = thumbs.length,
123405             inputEl = owner.inputEl,
123406             innerEl = owner.innerEl,
123407             endEl = owner.endEl,
123408             i = 0;
123409
123410         /*
123411          * If we happen to be animating during a resize, the position of the thumb will likely be off
123412          * when the animation stops. As such, just stop any animations before syncing the thumbs.
123413          */
123414         for(; i < length; ++i) {
123415             thumbs[i].el.stopAnimation();
123416         }
123417         
123418         if (owner.vertical) {
123419             inputEl.setHeight(height);
123420             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
123421         }
123422         else {
123423             inputEl.setWidth(width);
123424             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
123425         }
123426         owner.syncThumbs();
123427     }
123428 });
123429
123430 /**
123431  * @class Ext.layout.container.Absolute
123432  * @extends Ext.layout.container.Anchor
123433  *
123434  * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
123435  * ability for x/y positioning using the standard x and y component config options.
123436  *
123437  * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
123438  * configuration property.  See {@link Ext.container.Container#layout} for additional details.
123439  *
123440  *     @example
123441  *     Ext.create('Ext.form.Panel', {
123442  *         title: 'Absolute Layout',
123443  *         width: 300,
123444  *         height: 275,
123445  *         layout:'absolute',
123446  *         layoutConfig: {
123447  *             // layout-specific configs go here
123448  *             //itemCls: 'x-abs-layout-item',
123449  *         },
123450  *         url:'save-form.php',
123451  *         defaultType: 'textfield',
123452  *         items: [{
123453  *             x: 10,
123454  *             y: 10,
123455  *             xtype:'label',
123456  *             text: 'Send To:'
123457  *         },{
123458  *             x: 80,
123459  *             y: 10,
123460  *             name: 'to',
123461  *             anchor:'90%'  // anchor width by percentage
123462  *         },{
123463  *             x: 10,
123464  *             y: 40,
123465  *             xtype:'label',
123466  *             text: 'Subject:'
123467  *         },{
123468  *             x: 80,
123469  *             y: 40,
123470  *             name: 'subject',
123471  *             anchor: '90%'  // anchor width by percentage
123472  *         },{
123473  *             x:0,
123474  *             y: 80,
123475  *             xtype: 'textareafield',
123476  *             name: 'msg',
123477  *             anchor: '100% 100%'  // anchor width and height
123478  *         }],
123479  *         renderTo: Ext.getBody()
123480  *     });
123481  */
123482 Ext.define('Ext.layout.container.Absolute', {
123483
123484     /* Begin Definitions */
123485
123486     alias: 'layout.absolute',
123487     extend: 'Ext.layout.container.Anchor',
123488     alternateClassName: 'Ext.layout.AbsoluteLayout',
123489
123490     /* End Definitions */
123491
123492     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
123493
123494     type: 'absolute',
123495
123496     onLayout: function() {
123497         var me = this,
123498             target = me.getTarget(),
123499             targetIsBody = target.dom === document.body;
123500
123501         // Do not set position: relative; when the absolute layout target is the body
123502         if (!targetIsBody) {
123503             target.position();
123504         }
123505         me.paddingLeft = target.getPadding('l');
123506         me.paddingTop = target.getPadding('t');
123507         me.callParent(arguments);
123508     },
123509
123510     // private
123511     adjustWidthAnchor: function(value, comp) {
123512         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
123513         return value ? value - comp.getPosition(true)[0] : value;
123514     },
123515
123516     // private
123517     adjustHeightAnchor: function(value, comp) {
123518         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
123519         return value ? value - comp.getPosition(true)[1] : value;
123520     }
123521 });
123522 /**
123523  * @class Ext.layout.container.Accordion
123524  * @extends Ext.layout.container.VBox
123525  *
123526  * This is a layout that manages multiple Panels in an expandable accordion style such that only
123527  * **one Panel can be expanded at any given time**. Each Panel has built-in support for expanding and collapsing.
123528  *
123529  * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
123530  *
123531  *     @example
123532  *     Ext.create('Ext.panel.Panel', {
123533  *         title: 'Accordion Layout',
123534  *         width: 300,
123535  *         height: 300,
123536  *         layout:'accordion',
123537  *         defaults: {
123538  *             // applied to each contained panel
123539  *             bodyStyle: 'padding:15px'
123540  *         },
123541  *         layoutConfig: {
123542  *             // layout-specific configs go here
123543  *             titleCollapse: false,
123544  *             animate: true,
123545  *             activeOnTop: true
123546  *         },
123547  *         items: [{
123548  *             title: 'Panel 1',
123549  *             html: 'Panel content!'
123550  *         },{
123551  *             title: 'Panel 2',
123552  *             html: 'Panel content!'
123553  *         },{
123554  *             title: 'Panel 3',
123555  *             html: 'Panel content!'
123556  *         }],
123557  *         renderTo: Ext.getBody()
123558  *     });
123559  */
123560 Ext.define('Ext.layout.container.Accordion', {
123561     extend: 'Ext.layout.container.VBox',
123562     alias: ['layout.accordion'],
123563     alternateClassName: 'Ext.layout.AccordionLayout',
123564
123565     itemCls: Ext.baseCSSPrefix + 'box-item ' + Ext.baseCSSPrefix + 'accordion-item',
123566
123567     align: 'stretch',
123568
123569     /**
123570      * @cfg {Boolean} fill
123571      * True to adjust the active item's height to fill the available space in the container, false to use the
123572      * item's current height, or auto height if not explicitly set.
123573      */
123574     fill : true,
123575
123576     /**
123577      * @cfg {Boolean} autoWidth
123578      * Child Panels have their width actively managed to fit within the accordion's width.
123579      * @deprecated This config is ignored in ExtJS 4
123580      */
123581     autoWidth : true,
123582
123583     /**
123584      * @cfg {Boolean} titleCollapse
123585      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
123586      * expand/collapse only when the toggle tool button is clicked.  When set to false,
123587      * {@link #hideCollapseTool} should be false also.
123588      */
123589     titleCollapse : true,
123590
123591     /**
123592      * @cfg {Boolean} hideCollapseTool
123593      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
123594      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
123595      */
123596     hideCollapseTool : false,
123597
123598     /**
123599      * @cfg {Boolean} collapseFirst
123600      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
123601      * in the contained Panels' title bars, false to render it last.
123602      */
123603     collapseFirst : false,
123604
123605     /**
123606      * @cfg {Boolean} animate
123607      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
123608      * close directly with no animation. Note: The layout performs animated collapsing
123609      * and expanding, <i>not</i> the child Panels.
123610      */
123611     animate : true,
123612     /**
123613      * @cfg {Boolean} activeOnTop
123614      * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
123615      *
123616      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
123617      * false to keep the panels in the rendered order.
123618      */
123619     activeOnTop : false,
123620     /**
123621      * @cfg {Boolean} multi
123622      * Set to <code>true</code> to enable multiple accordion items to be open at once.
123623      */
123624     multi: false,
123625
123626     constructor: function() {
123627         var me = this;
123628
123629         me.callParent(arguments);
123630
123631         // animate flag must be false during initial render phase so we don't get animations.
123632         me.initialAnimate = me.animate;
123633         me.animate = false;
123634
123635         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
123636         if (me.fill === false) {
123637             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
123638         }
123639     },
123640
123641     // Cannot lay out a fitting accordion before we have been allocated a height.
123642     // So during render phase, layout will not be performed.
123643     beforeLayout: function() {
123644         var me = this;
123645
123646         me.callParent(arguments);
123647         if (me.fill) {
123648             if (!(me.owner.el.dom.style.height || me.getLayoutTargetSize().height)) {
123649                 return false;
123650             }
123651         } else {
123652             me.owner.componentLayout.monitorChildren = false;
123653             me.autoSize = true;
123654             me.owner.setAutoScroll(true);
123655         }
123656     },
123657
123658     renderItems : function(items, target) {
123659         var me = this,
123660             ln = items.length,
123661             i = 0,
123662             comp,
123663             targetSize = me.getLayoutTargetSize(),
123664             renderedPanels = [];
123665
123666         for (; i < ln; i++) {
123667             comp = items[i];
123668             if (!comp.rendered) {
123669                 renderedPanels.push(comp);
123670
123671                 // Set up initial properties for Panels in an accordion.
123672                 if (me.collapseFirst) {
123673                     comp.collapseFirst = me.collapseFirst;
123674                 }
123675                 if (me.hideCollapseTool) {
123676                     comp.hideCollapseTool = me.hideCollapseTool;
123677                     comp.titleCollapse = true;
123678                 }
123679                 else if (me.titleCollapse) {
123680                     comp.titleCollapse = me.titleCollapse;
123681                 }
123682
123683                 delete comp.hideHeader;
123684                 comp.collapsible = true;
123685                 comp.title = comp.title || '&#160;';
123686
123687                 // Set initial sizes
123688                 comp.width = targetSize.width;
123689                 if (me.fill) {
123690                     delete comp.height;
123691                     delete comp.flex;
123692
123693                     // If there is an expanded item, all others must be rendered collapsed.
123694                     if (me.expandedItem !== undefined) {
123695                         comp.collapsed = true;
123696                     }
123697                     // Otherwise expand the first item with collapsed explicitly configured as false
123698                     else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
123699                         comp.flex = 1;
123700                         me.expandedItem = i;
123701                     } else {
123702                         comp.collapsed = true;
123703                     }
123704                     // If we are fitting, then intercept expand/collapse requests.
123705                     me.owner.mon(comp, {
123706                         show: me.onComponentShow,
123707                         beforeexpand: me.onComponentExpand,
123708                         beforecollapse: me.onComponentCollapse,
123709                         scope: me
123710                     });
123711                 } else {
123712                     delete comp.flex;
123713                     comp.animCollapse = me.initialAnimate;
123714                     comp.autoHeight = true;
123715                     comp.autoScroll = false;
123716                 }
123717                 comp.border = comp.collapsed;
123718             }
123719         }
123720
123721         // If no collapsed:false Panels found, make the first one expanded.
123722         if (ln && me.expandedItem === undefined) {
123723             me.expandedItem = 0;
123724             comp = items[0];
123725             comp.collapsed = comp.border = false;
123726             if (me.fill) {
123727                 comp.flex = 1;
123728             }
123729         }
123730
123731         // Render all Panels.
123732         me.callParent(arguments);
123733
123734         // Postprocess rendered Panels.
123735         ln = renderedPanels.length;
123736         for (i = 0; i < ln; i++) {
123737             comp = renderedPanels[i];
123738
123739             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
123740             delete comp.width;
123741
123742             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
123743             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
123744         }
123745     },
123746
123747     onLayout: function() {
123748         var me = this;
123749
123750
123751         if (me.fill) {
123752             me.callParent(arguments);
123753         } else {
123754             var targetSize = me.getLayoutTargetSize(),
123755                 items = me.getVisibleItems(),
123756                 len = items.length,
123757                 i = 0, comp;
123758
123759             for (; i < len; i++) {
123760                 comp = items[i];
123761                 if (comp.collapsed) {
123762                     items[i].setWidth(targetSize.width);
123763                 } else {
123764                     items[i].setSize(null, null);
123765                 }
123766             }
123767         }
123768         me.updatePanelClasses();
123769
123770         return me;
123771     },
123772
123773     updatePanelClasses: function() {
123774         var children = this.getLayoutItems(),
123775             ln = children.length,
123776             siblingCollapsed = true,
123777             i, child;
123778
123779         for (i = 0; i < ln; i++) {
123780             child = children[i];
123781
123782             // Fix for EXTJSIV-3724. Windows only.
123783             // Collapsing the Psnel's el to a size which only allows a single hesder to be visible, scrolls the header out of view.
123784             if (Ext.isWindows) {
123785                 child.el.dom.scrollTop = 0;
123786             }
123787
123788             if (siblingCollapsed) {
123789                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
123790             }
123791             else {
123792                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
123793             }
123794
123795             if (i + 1 == ln && child.collapsed) {
123796                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
123797             }
123798             else {
123799                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
123800             }
123801             siblingCollapsed = child.collapsed;
123802         }
123803     },
123804     
123805     animCallback: function(){
123806         Ext.Array.forEach(this.toCollapse, function(comp){
123807             comp.fireEvent('collapse', comp);
123808         });
123809         
123810         Ext.Array.forEach(this.toExpand, function(comp){
123811             comp.fireEvent('expand', comp);
123812         });    
123813     },
123814     
123815     setupEvents: function(){
123816         this.toCollapse = [];
123817         this.toExpand = [];    
123818     },
123819
123820     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
123821     // their headers.
123822     // The expanded Component receives the only flex value, and so gets all remaining space.
123823     onComponentExpand: function(toExpand) {
123824         var me = this,
123825             it = me.owner.items.items,
123826             len = it.length,
123827             i = 0,
123828             comp;
123829
123830         me.setupEvents();
123831         for (; i < len; i++) {
123832             comp = it[i];
123833             if (comp === toExpand && comp.collapsed) {
123834                 me.setExpanded(comp);
123835             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
123836                 me.setCollapsed(comp);
123837             }
123838         }
123839
123840         me.animate = me.initialAnimate;
123841         if (me.activeOnTop) {
123842             // insert will trigger a layout
123843             me.owner.insert(0, toExpand); 
123844         } else {
123845             me.layout();
123846         }
123847         me.animate = false;
123848         return false;
123849     },
123850
123851     onComponentCollapse: function(comp) {
123852         var me = this,
123853             toExpand = comp.next() || comp.prev(),
123854             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
123855
123856         me.setupEvents();
123857         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
123858         // then ask the box layout to collapse it to its header.
123859         if (me.multi) {
123860             me.setCollapsed(comp);
123861
123862             // If the collapsing Panel is the only expanded one, expand the following Component.
123863             // All this is handling fill: true, so there must be at least one expanded,
123864             if (expanded.length === 1 && expanded[0] === comp) {
123865                 me.setExpanded(toExpand);
123866             }
123867
123868             me.animate = me.initialAnimate;
123869             me.layout();
123870             me.animate = false;
123871         }
123872         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
123873         else if (toExpand) {
123874             me.onComponentExpand(toExpand);
123875         }
123876         return false;
123877     },
123878
123879     onComponentShow: function(comp) {
123880         // Showing a Component means that you want to see it, so expand it.
123881         this.onComponentExpand(comp);
123882     },
123883
123884     setCollapsed: function(comp) {
123885         var otherDocks = comp.getDockedItems(),
123886             dockItem,
123887             len = otherDocks.length,
123888             i = 0;
123889
123890         // Hide all docked items except the header
123891         comp.hiddenDocked = [];
123892         for (; i < len; i++) {
123893             dockItem = otherDocks[i];
123894             if ((dockItem !== comp.header) && !dockItem.hidden) {
123895                 dockItem.hidden = true;
123896                 comp.hiddenDocked.push(dockItem);
123897             }
123898         }
123899         comp.addCls(comp.collapsedCls);
123900         comp.header.addCls(comp.collapsedHeaderCls);
123901         comp.height = comp.header.getHeight();
123902         comp.el.setHeight(comp.height);
123903         comp.collapsed = true;
123904         delete comp.flex;
123905         if (this.initialAnimate) {
123906             this.toCollapse.push(comp);
123907         } else {
123908             comp.fireEvent('collapse', comp);
123909         }
123910         if (comp.collapseTool) {
123911             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
123912         }
123913     },
123914
123915     setExpanded: function(comp) {
123916         var otherDocks = comp.hiddenDocked,
123917             len = otherDocks ? otherDocks.length : 0,
123918             i = 0;
123919
123920         // Show temporarily hidden docked items
123921         for (; i < len; i++) {
123922             otherDocks[i].show();
123923         }
123924
123925         // If it was an initial native collapse which hides the body
123926         if (!comp.body.isVisible()) {
123927             comp.body.show();
123928         }
123929         delete comp.collapsed;
123930         delete comp.height;
123931         delete comp.componentLayout.lastComponentSize;
123932         comp.suspendLayout = false;
123933         comp.flex = 1;
123934         comp.removeCls(comp.collapsedCls);
123935         comp.header.removeCls(comp.collapsedHeaderCls);
123936          if (this.initialAnimate) {
123937             this.toExpand.push(comp);
123938         } else {
123939             comp.fireEvent('expand', comp);
123940         }
123941         if (comp.collapseTool) {
123942             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
123943         }
123944         comp.setAutoScroll(comp.initialConfig.autoScroll);
123945     }
123946 });
123947 /**
123948  * This class functions between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
123949  * layout to resize both immediate siblings.
123950  *
123951  * By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
123952  * `{@link Ext.Component#maintainFlex maintainFlex}: true` which will cause it not to receive a new size explicitly, but to be resized
123953  * by the layout.
123954  *
123955  * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
123956  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
123957  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
123958  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.
123959  */
123960 Ext.define('Ext.resizer.Splitter', {
123961     extend: 'Ext.Component',
123962     requires: ['Ext.XTemplate'],
123963     uses: ['Ext.resizer.SplitterTracker'],
123964     alias: 'widget.splitter',
123965
123966     renderTpl: [
123967         '<tpl if="collapsible===true">',
123968             '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
123969                     Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&nbsp;</div>',
123970         '</tpl>'
123971     ],
123972
123973     baseCls: Ext.baseCSSPrefix + 'splitter',
123974     collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
123975
123976     /**
123977      * @cfg {Boolean} collapsible
123978      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
123979      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
123980      */
123981     collapsible: false,
123982
123983     /**
123984      * @cfg {Boolean} performCollapse
123985      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
123986      * state of the {@link #collapseTarget}.</p>
123987      */
123988
123989     /**
123990      * @cfg {Boolean} collapseOnDblClick
123991      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
123992      */
123993     collapseOnDblClick: true,
123994
123995     /**
123996      * @cfg {Number} defaultSplitMin
123997      * Provides a default minimum width or height for the two components
123998      * that the splitter is between.
123999      */
124000     defaultSplitMin: 40,
124001
124002     /**
124003      * @cfg {Number} defaultSplitMax
124004      * Provides a default maximum width or height for the two components
124005      * that the splitter is between.
124006      */
124007     defaultSplitMax: 1000,
124008
124009     /**
124010      * @cfg {String} collapsedCls
124011      * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
124012      */
124013
124014     width: 5,
124015     height: 5,
124016
124017     /**
124018      * @cfg {String/Ext.panel.Panel} collapseTarget
124019      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
124020      * <p>Or the immediate sibling Panel to collapse.</p>
124021      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
124022      * <p><b>Note that only Panels may be collapsed.</b></p>
124023      */
124024     collapseTarget: 'next',
124025
124026     /**
124027      * @property orientation
124028      * @type String
124029      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
124030      * when used in a vbox layout.
124031      */
124032
124033     onRender: function() {
124034         var me = this,
124035             target = me.getCollapseTarget(),
124036             collapseDir = me.getCollapseDirection();
124037
124038         Ext.applyIf(me.renderData, {
124039             collapseDir: collapseDir,
124040             collapsible: me.collapsible || target.collapsible
124041         });
124042
124043         me.addChildEls('collapseEl');
124044
124045         this.callParent(arguments);
124046
124047         // Add listeners on the mini-collapse tool unless performCollapse is set to false
124048         if (me.performCollapse !== false) {
124049             if (me.renderData.collapsible) {
124050                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
124051             }
124052             if (me.collapseOnDblClick) {
124053                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
124054             }
124055         }
124056
124057         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
124058         me.mon(target, 'collapse', me.onTargetCollapse, me);
124059         me.mon(target, 'expand', me.onTargetExpand, me);
124060
124061         me.el.addCls(me.baseCls + '-' + me.orientation);
124062         me.el.unselectable();
124063
124064         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
124065             el: me.el
124066         });
124067
124068         // Relay the most important events to our owner (could open wider later):
124069         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
124070     },
124071
124072     getCollapseDirection: function() {
124073         var me = this,
124074             idx,
124075             type = me.ownerCt.layout.type;
124076
124077         // Avoid duplication of string tests.
124078         // Create a two bit truth table of the configuration of the Splitter:
124079         // Collapse Target | orientation
124080         //        0              0             = next, horizontal
124081         //        0              1             = next, vertical
124082         //        1              0             = prev, horizontal
124083         //        1              1             = prev, vertical
124084         if (me.collapseTarget.isComponent) {
124085             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
124086         } else {
124087             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
124088         }
124089
124090         // Read the data out the truth table
124091         me.orientation = ['horizontal', 'vertical'][idx & 1];
124092         return ['bottom', 'right', 'top', 'left'][idx];
124093     },
124094
124095     getCollapseTarget: function() {
124096         var me = this;
124097
124098         return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
124099     },
124100
124101     onTargetCollapse: function(target) {
124102         this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
124103     },
124104
124105     onTargetExpand: function(target) {
124106         this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
124107     },
124108
124109     toggleTargetCmp: function(e, t) {
124110         var cmp = this.getCollapseTarget();
124111
124112         if (cmp.isVisible()) {
124113             // restore
124114             if (cmp.collapsed) {
124115                 cmp.expand(cmp.animCollapse);
124116             // collapse
124117             } else {
124118                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
124119             }
124120         }
124121     },
124122
124123     /*
124124      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
124125      */
124126     setSize: function() {
124127         var me = this;
124128         me.callParent(arguments);
124129         if (Ext.isIE) {
124130             me.el.repaint();
124131         }
124132     }
124133 });
124134
124135 /**
124136  * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
124137  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
124138  *
124139  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
124140  * config, and should generally not need to be created directly via the new keyword.
124141  *
124142  *     @example
124143  *     Ext.create('Ext.panel.Panel', {
124144  *         width: 500,
124145  *         height: 400,
124146  *         title: 'Border Layout',
124147  *         layout: 'border',
124148  *         items: [{
124149  *             title: 'South Region is resizable',
124150  *             region: 'south',     // position for region
124151  *             xtype: 'panel',
124152  *             height: 100,
124153  *             split: true,         // enable resizing
124154  *             margins: '0 5 5 5'
124155  *         },{
124156  *             // xtype: 'panel' implied by default
124157  *             title: 'West Region is collapsible',
124158  *             region:'west',
124159  *             xtype: 'panel',
124160  *             margins: '5 0 0 5',
124161  *             width: 200,
124162  *             collapsible: true,   // make collapsible
124163  *             id: 'west-region-container',
124164  *             layout: 'fit'
124165  *         },{
124166  *             title: 'Center Region',
124167  *             region: 'center',     // center region is required, no width/height specified
124168  *             xtype: 'panel',
124169  *             layout: 'fit',
124170  *             margins: '5 5 0 0'
124171  *         }],
124172  *         renderTo: Ext.getBody()
124173  *     });
124174  *
124175  * # Notes
124176  *
124177  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
124178  *   The child item in the center region will always be resized to fill the remaining space
124179  *   not used by the other regions in the layout.
124180  *
124181  * - Any child items with a region of `west` or `east` may be configured with either an initial
124182  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
124183  *   **string** (Which is simply divided by 100 and used as a flex value).
124184  *   The 'center' region has a flex value of `1`.
124185  *
124186  * - Any child items with a region of `north` or `south` may be configured with either an initial
124187  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
124188  *   **string** (Which is simply divided by 100 and used as a flex value).
124189  *   The 'center' region has a flex value of `1`.
124190  *
124191  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
124192  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
124193  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
124194  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
124195  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
124196  *   the west region:
124197  *
124198  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
124199  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
124200  *       wrc.{@link Ext.container.Container#add add}({
124201  *           title: 'Added Panel',
124202  *           html: 'Some content'
124203  *       });
124204  *
124205  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
124206  */
124207 Ext.define('Ext.layout.container.Border', {
124208
124209     alias: ['layout.border'],
124210     extend: 'Ext.layout.container.Container',
124211     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
124212     alternateClassName: 'Ext.layout.BorderLayout',
124213
124214     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
124215
124216     itemCls: Ext.baseCSSPrefix + 'border-item',
124217
124218     bindToOwnerCtContainer: true,
124219
124220     percentageRe: /(\d+)%/,
124221
124222     slideDirection: {
124223         north: 't',
124224         south: 'b',
124225         west: 'l',
124226         east: 'r'
124227     },
124228
124229     constructor: function(config) {
124230         this.initialConfig = config;
124231         this.callParent(arguments);
124232     },
124233
124234     onLayout: function() {
124235         var me = this;
124236         if (!me.borderLayoutInitialized) {
124237             me.initializeBorderLayout();
124238         }
124239
124240         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
124241         me.fixHeightConstraints();
124242         me.shadowLayout.onLayout();
124243         if (me.embeddedContainer) {
124244             me.embeddedContainer.layout.onLayout();
124245         }
124246
124247         // If the panel was originally configured with collapsed: true, it will have
124248         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
124249         if (!me.initialCollapsedComplete) {
124250             Ext.iterate(me.regions, function(name, region){
124251                 if (region.borderCollapse) {
124252                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
124253                 }
124254             });
124255             me.initialCollapsedComplete = true;
124256         }
124257     },
124258
124259     isValidParent : function(item, target, position) {
124260         if (!this.borderLayoutInitialized) {
124261             this.initializeBorderLayout();
124262         }
124263
124264         // Delegate this operation to the shadow "V" or "H" box layout.
124265         return this.shadowLayout.isValidParent(item, target, position);
124266     },
124267
124268     beforeLayout: function() {
124269         if (!this.borderLayoutInitialized) {
124270             this.initializeBorderLayout();
124271         }
124272
124273         // Delegate this operation to the shadow "V" or "H" box layout.
124274         this.shadowLayout.beforeLayout();
124275
124276         // note: don't call base because that does a renderItems again
124277     },
124278
124279     renderItems: function(items, target) {
124280         Ext.Error.raise('This should not be called');
124281     },
124282
124283     renderItem: function(item) {
124284         Ext.Error.raise('This should not be called');
124285     },
124286
124287     renderChildren: function() {
124288         if (!this.borderLayoutInitialized) {
124289             this.initializeBorderLayout();
124290         }
124291
124292         this.shadowLayout.renderChildren();
124293     },
124294
124295     /*
124296      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
124297      * We must not include child items which are floated over the layout (are primed with a slide out animation)
124298      */
124299     getVisibleItems: function() {
124300         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
124301     },
124302
124303     initializeBorderLayout: function() {
124304         var me = this,
124305             i = 0,
124306             items = me.getLayoutItems(),
124307             ln = items.length,
124308             regions = (me.regions = {}),
124309             vBoxItems = [],
124310             hBoxItems = [],
124311             horizontalFlex = 0,
124312             verticalFlex = 0,
124313             comp, percentage;
124314
124315         // Map of Splitters for each region
124316         me.splitters = {};
124317
124318         // Map of regions
124319         for (; i < ln; i++) {
124320             comp = items[i];
124321             regions[comp.region] = comp;
124322
124323             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
124324             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
124325
124326                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
124327                 comp.borderCollapse = comp.collapsed;
124328                 comp.collapsed = false;
124329
124330                 comp.on({
124331                     beforecollapse: me.onBeforeRegionCollapse,
124332                     beforeexpand: me.onBeforeRegionExpand,
124333                     destroy: me.onRegionDestroy,
124334                     scope: me
124335                 });
124336                 me.setupState(comp);
124337             }
124338         }
124339         if (!regions.center) {
124340             Ext.Error.raise("You must specify a center region when defining a BorderLayout.");
124341         }
124342         comp = regions.center;
124343         if (!comp.flex) {
124344             comp.flex = 1;
124345         }
124346         delete comp.width;
124347         comp.maintainFlex = true;
124348
124349         // Begin the VBox and HBox item list.
124350         comp = regions.west;
124351         if (comp) {
124352             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
124353             hBoxItems.push(comp);
124354             if (comp.split) {
124355                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
124356             }
124357             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
124358             if (percentage) {
124359                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124360                 delete comp.width;
124361             }
124362         }
124363         comp = regions.north;
124364         if (comp) {
124365             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
124366             vBoxItems.push(comp);
124367             if (comp.split) {
124368                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
124369             }
124370             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
124371             if (percentage) {
124372                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124373                 delete comp.height;
124374             }
124375         }
124376
124377         // Decide into which Collection the center region goes.
124378         if (regions.north || regions.south) {
124379             if (regions.east || regions.west) {
124380
124381                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
124382                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
124383                     xtype: 'container',
124384                     region: 'center',
124385                     id: me.owner.id + '-embedded-center',
124386                     cls: Ext.baseCSSPrefix + 'border-item',
124387                     flex: regions.center.flex,
124388                     maintainFlex: true,
124389                     layout: {
124390                         type: 'hbox',
124391                         align: 'stretch',
124392                         getVisibleItems: me.getVisibleItems
124393                     }
124394                 }));
124395                 hBoxItems.push(regions.center);
124396             }
124397             // No east or west: the original center goes straight into the vbox
124398             else {
124399                 vBoxItems.push(regions.center);
124400             }
124401         }
124402         // If we have no north or south, then the center is part of the HBox items
124403         else {
124404             hBoxItems.push(regions.center);
124405         }
124406
124407         // Finish off the VBox and HBox item list.
124408         comp = regions.south;
124409         if (comp) {
124410             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
124411             if (comp.split) {
124412                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
124413             }
124414             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
124415             if (percentage) {
124416                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124417                 delete comp.height;
124418             }
124419             vBoxItems.push(comp);
124420         }
124421         comp = regions.east;
124422         if (comp) {
124423             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
124424             if (comp.split) {
124425                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
124426             }
124427             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
124428             if (percentage) {
124429                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
124430                 delete comp.width;
124431             }
124432             hBoxItems.push(comp);
124433         }
124434
124435         // Create the injected "items" collections for the Containers.
124436         // If we have north or south, then the shadow Container will be a VBox.
124437         // If there are also east or west regions, its center will be a shadow HBox.
124438         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
124439         if (regions.north || regions.south) {
124440
124441             me.shadowContainer = Ext.create('Ext.container.Container', {
124442                 ownerCt: me.owner,
124443                 el: me.getTarget(),
124444                 layout: Ext.applyIf({
124445                     type: 'vbox',
124446                     align: 'stretch',
124447                     getVisibleItems: me.getVisibleItems
124448                 }, me.initialConfig)
124449             });
124450             me.createItems(me.shadowContainer, vBoxItems);
124451
124452             // Allow the Splitters to orientate themselves
124453             if (me.splitters.north) {
124454                 me.splitters.north.ownerCt = me.shadowContainer;
124455             }
124456             if (me.splitters.south) {
124457                 me.splitters.south.ownerCt = me.shadowContainer;
124458             }
124459
124460             // Inject items into the HBox Container if there is one - if there was an east or west.
124461             if (me.embeddedContainer) {
124462                 me.embeddedContainer.ownerCt = me.shadowContainer;
124463                 me.createItems(me.embeddedContainer, hBoxItems);
124464
124465                 // Allow the Splitters to orientate themselves
124466                 if (me.splitters.east) {
124467                     me.splitters.east.ownerCt = me.embeddedContainer;
124468                 }
124469                 if (me.splitters.west) {
124470                     me.splitters.west.ownerCt = me.embeddedContainer;
124471                 }
124472
124473                 // These spliiters need to be constrained by components one-level below
124474                 // the component in their vobx. We update the min/maxHeight on the helper
124475                 // (embeddedContainer) prior to starting the split/drag. This has to be
124476                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
124477                 // dynamically.
124478                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
124479                     if (splitter) {
124480                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
124481                     }
124482                 });
124483
124484                 // The east or west region wanted a percentage
124485                 if (horizontalFlex) {
124486                     regions.center.flex -= horizontalFlex;
124487                 }
124488                 // The north or south region wanted a percentage
124489                 if (verticalFlex) {
124490                     me.embeddedContainer.flex -= verticalFlex;
124491                 }
124492             } else {
124493                 // The north or south region wanted a percentage
124494                 if (verticalFlex) {
124495                     regions.center.flex -= verticalFlex;
124496                 }
124497             }
124498         }
124499         // If we have no north or south, then there's only one Container, and it's
124500         // an HBox, or, if only a center region was specified, a Fit.
124501         else {
124502             me.shadowContainer = Ext.create('Ext.container.Container', {
124503                 ownerCt: me.owner,
124504                 el: me.getTarget(),
124505                 layout: Ext.applyIf({
124506                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
124507                     align: 'stretch'
124508                 }, me.initialConfig)
124509             });
124510             me.createItems(me.shadowContainer, hBoxItems);
124511
124512             // Allow the Splitters to orientate themselves
124513             if (me.splitters.east) {
124514                 me.splitters.east.ownerCt = me.shadowContainer;
124515             }
124516             if (me.splitters.west) {
124517                 me.splitters.west.ownerCt = me.shadowContainer;
124518             }
124519
124520             // The east or west region wanted a percentage
124521             if (horizontalFlex) {
124522                 regions.center.flex -= verticalFlex;
124523             }
124524         }
124525
124526         // Create upward links from the region Components to their shadow ownerCts
124527         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
124528             items[i].shadowOwnerCt = me.shadowContainer;
124529         }
124530         if (me.embeddedContainer) {
124531             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
124532                 items[i].shadowOwnerCt = me.embeddedContainer;
124533             }
124534         }
124535
124536         // This is the layout that we delegate all operations to
124537         me.shadowLayout = me.shadowContainer.getLayout();
124538
124539         me.borderLayoutInitialized = true;
124540     },
124541
124542     setupState: function(comp){
124543         var getState = comp.getState;
124544         comp.getState = function(){
124545             // call the original getState
124546             var state = getState.call(comp) || {},
124547                 region = comp.region;
124548
124549             state.collapsed = !!comp.collapsed;
124550             if (region == 'west' || region == 'east') {
124551                 state.width = comp.getWidth();
124552             } else {
124553                 state.height = comp.getHeight();
124554             }
124555             return state;
124556         };
124557         comp.addStateEvents(['collapse', 'expand', 'resize']);
124558     },
124559
124560     /**
124561      * Create the items collection for our shadow/embedded containers
124562      * @private
124563      */
124564     createItems: function(container, items){
124565         // Have to inject an items Collection *after* construction.
124566         // The child items of the shadow layout must retain their original, user-defined ownerCt
124567         delete container.items;
124568         container.initItems();
124569         container.items.addAll(items);
124570     },
124571
124572     // Private
124573     // Create a splitter for a child of the layout.
124574     createSplitter: function(comp) {
124575         var me = this,
124576             interceptCollapse = (comp.collapseMode != 'header'),
124577             resizer;
124578
124579         resizer = Ext.create('Ext.resizer.Splitter', {
124580             hidden: !!comp.hidden,
124581             collapseTarget: comp,
124582             performCollapse: !interceptCollapse,
124583             listeners: interceptCollapse ? {
124584                 click: {
124585                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
124586                     element: 'collapseEl'
124587                 }
124588             } : null
124589         });
124590
124591         // Mini collapse means that the splitter is the placeholder Component
124592         if (comp.collapseMode == 'mini') {
124593             comp.placeholder = resizer;
124594             resizer.collapsedCls = comp.collapsedCls;
124595         }
124596
124597         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
124598         comp.on({
124599             hide: me.onRegionVisibilityChange,
124600             show: me.onRegionVisibilityChange,
124601             scope: me
124602         });
124603         return resizer;
124604     },
124605
124606     // Private
124607     // Propagates the min/maxHeight values from the inner hbox items to its container.
124608     fixHeightConstraints: function () {
124609         var me = this,
124610             ct = me.embeddedContainer,
124611             maxHeight = 1e99, minHeight = -1;
124612
124613         if (!ct) {
124614             return;
124615         }
124616
124617         ct.items.each(function (item) {
124618             if (Ext.isNumber(item.maxHeight)) {
124619                 maxHeight = Math.max(maxHeight, item.maxHeight);
124620             }
124621             if (Ext.isNumber(item.minHeight)) {
124622                 minHeight = Math.max(minHeight, item.minHeight);
124623             }
124624         });
124625
124626         ct.maxHeight = maxHeight;
124627         ct.minHeight = minHeight;
124628     },
124629
124630     // Hide/show a region's associated splitter when the region is hidden/shown
124631     onRegionVisibilityChange: function(comp){
124632         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
124633         this.layout();
124634     },
124635
124636     // Called when a splitter mini-collapse tool is clicked on.
124637     // The listener is only added if this layout is controlling collapsing,
124638     // not if the component's collapseMode is 'mini' or 'header'.
124639     onSplitterCollapseClick: function(comp) {
124640         if (comp.collapsed) {
124641             this.onPlaceHolderToolClick(null, null, null, {client: comp});
124642         } else {
124643             comp.collapse();
124644         }
124645     },
124646
124647     /**
124648      * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
124649      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
124650      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
124651      *
124652      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
124653      * collapsed.**
124654      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
124655      * Ext.panel.Panel#placeholder placeholder}.
124656      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
124657      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
124658      */
124659     getPlaceholder: function(comp) {
124660         var me = this,
124661             placeholder = comp.placeholder,
124662             shadowContainer = comp.shadowOwnerCt,
124663             shadowLayout = shadowContainer.layout,
124664             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
124665             horiz = (comp.region == 'north' || comp.region == 'south');
124666
124667         // No placeholder if the collapse mode is not the Border layout default
124668         if (comp.collapseMode == 'header') {
124669             return;
124670         }
124671
124672         // Provide a replacement Container with an expand tool
124673         if (!placeholder) {
124674             if (comp.collapseMode == 'mini') {
124675                 placeholder = Ext.create('Ext.resizer.Splitter', {
124676                     id: 'collapse-placeholder-' + comp.id,
124677                     collapseTarget: comp,
124678                     performCollapse: false,
124679                     listeners: {
124680                         click: {
124681                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
124682                             element: 'collapseEl'
124683                         }
124684                     }
124685                 });
124686                 placeholder.addCls(placeholder.collapsedCls);
124687             } else {
124688                 placeholder = {
124689                     id: 'collapse-placeholder-' + comp.id,
124690                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
124691                     xtype: 'header',
124692                     orientation: horiz ? 'horizontal' : 'vertical',
124693                     title: comp.title,
124694                     textCls: comp.headerTextCls,
124695                     iconCls: comp.iconCls,
124696                     baseCls: comp.baseCls + '-header',
124697                     ui: comp.ui,
124698                     indicateDrag: comp.draggable,
124699                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
124700                     listeners: comp.floatable ? {
124701                         click: {
124702                             fn: function(e) {
124703                                 me.floatCollapsedPanel(e, comp);
124704                             },
124705                             element: 'el'
124706                         }
124707                     } : null
124708                 };
124709                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
124710                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
124711                     placeholder.width = 25;
124712                 }
124713                 if (!comp.hideCollapseTool) {
124714                     placeholder[horiz ? 'tools' : 'items'] = [{
124715                         xtype: 'tool',
124716                         client: comp,
124717                         type: 'expand-' + oppositeDirection,
124718                         handler: me.onPlaceHolderToolClick,
124719                         scope: me
124720                     }];
124721                 }
124722             }
124723             placeholder = me.owner.createComponent(placeholder);
124724             if (comp.isXType('panel')) {
124725                 comp.on({
124726                     titlechange: me.onRegionTitleChange,
124727                     iconchange: me.onRegionIconChange,
124728                     scope: me
124729                 });
124730             }
124731         }
124732
124733         // The collapsed Component holds a reference to its placeholder and vice versa
124734         comp.placeholder = placeholder;
124735         placeholder.comp = comp;
124736
124737         return placeholder;
124738     },
124739
124740     /**
124741      * @private
124742      * Update the placeholder title when panel title has been set or changed.
124743      */
124744     onRegionTitleChange: function(comp, newTitle) {
124745         comp.placeholder.setTitle(newTitle);
124746     },
124747
124748     /**
124749      * @private
124750      * Update the placeholder iconCls when panel iconCls has been set or changed.
124751      */
124752     onRegionIconChange: function(comp, newIconCls) {
124753         comp.placeholder.setIconCls(newIconCls);
124754     },
124755
124756     /**
124757      * @private
124758      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
124759      * when configured with a flex, calls this method on its ownerCt's layout.
124760      * @param {Ext.Component} child The child Component to calculate the box for
124761      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
124762      */
124763     calculateChildBox: function(comp) {
124764         var me = this;
124765         if (me.shadowContainer.items.contains(comp)) {
124766             return me.shadowContainer.layout.calculateChildBox(comp);
124767         }
124768         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
124769             return me.embeddedContainer.layout.calculateChildBox(comp);
124770         }
124771     },
124772
124773     /**
124774      * @private
124775      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
124776      * with a placeholder Header orientated in the appropriate dimension.
124777      * @param comp The Panel being collapsed.
124778      * @param direction
124779      * @param animate
124780      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
124781      */
124782     onBeforeRegionCollapse: function(comp, direction, animate) {
124783         if (comp.collapsedChangingLayout) {
124784             if (Ext.global.console && Ext.global.console.warn) {
124785                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
124786             }
124787             return false;
124788         }
124789         comp.collapsedChangingLayout = true;
124790         var me = this,
124791             compEl = comp.el,
124792             width,
124793             miniCollapse = comp.collapseMode == 'mini',
124794             shadowContainer = comp.shadowOwnerCt,
124795             shadowLayout = shadowContainer.layout,
124796             placeholder = comp.placeholder,
124797             sl = me.owner.suspendLayout,
124798             scsl = shadowContainer.suspendLayout,
124799             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
124800
124801         // Do not trigger a layout during transition to collapsed Component
124802         me.owner.suspendLayout = true;
124803         shadowContainer.suspendLayout = true;
124804
124805         // Prevent upward notifications from downstream layouts
124806         shadowLayout.layoutBusy = true;
124807         if (shadowContainer.componentLayout) {
124808             shadowContainer.componentLayout.layoutBusy = true;
124809         }
124810         me.shadowContainer.layout.layoutBusy = true;
124811         me.layoutBusy = true;
124812         me.owner.componentLayout.layoutBusy = true;
124813
124814         // Provide a replacement Container with an expand tool
124815         if (!placeholder) {
124816             placeholder = me.getPlaceholder(comp);
124817         }
124818
124819         // placeholder already in place; show it.
124820         if (placeholder.shadowOwnerCt === shadowContainer) {
124821             placeholder.show();
124822         }
124823         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
124824         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
124825         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
124826         else {
124827             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
124828             placeholder.shadowOwnerCt = shadowContainer;
124829             placeholder.ownerCt = me.owner;
124830         }
124831
124832         // Flag the collapsing Component as hidden and show the placeholder.
124833         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
124834         // We hide or slideOut the Component's element
124835         comp.hidden = true;
124836
124837         if (!placeholder.rendered) {
124838             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
124839
124840             // The inserted placeholder does not have the proper size, so copy the width
124841             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
124842             // without recursive layouts. This is only an issue initially. After this time,
124843             // placeholder will have the correct width/height set by the layout (which has
124844             // already happened when we get here initially).
124845             if (comp.region == 'north' || comp.region == 'south') {
124846                 placeholder.setCalculatedSize(comp.getWidth());
124847             } else {
124848                 placeholder.setCalculatedSize(undefined, comp.getHeight());
124849             }
124850         }
124851
124852         // Jobs to be done after the collapse has been done
124853         function afterCollapse() {
124854             // Reinstate automatic laying out.
124855             me.owner.suspendLayout = sl;
124856             shadowContainer.suspendLayout = scsl;
124857             delete shadowLayout.layoutBusy;
124858             if (shadowContainer.componentLayout) {
124859                 delete shadowContainer.componentLayout.layoutBusy;
124860             }
124861             delete me.shadowContainer.layout.layoutBusy;
124862             delete me.layoutBusy;
124863             delete me.owner.componentLayout.layoutBusy;
124864             delete comp.collapsedChangingLayout;
124865
124866             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
124867             comp.collapsed = true;
124868             comp.fireEvent('collapse', comp);
124869         }
124870
124871         /*
124872          * Set everything to the new positions. Note that we
124873          * only want to animate the collapse if it wasn't configured
124874          * initially with collapsed: true
124875          */
124876         if (comp.animCollapse && me.initialCollapsedComplete) {
124877             shadowLayout.layout();
124878             compEl.dom.style.zIndex = 100;
124879
124880             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124881             if (!miniCollapse) {
124882                 placeholder.el.hide();
124883             }
124884             compEl.slideOut(me.slideDirection[comp.region], {
124885                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124886                 listeners: {
124887                     afteranimate: function() {
124888                         compEl.show().setLeftTop(-10000, -10000);
124889                         compEl.dom.style.zIndex = '';
124890
124891                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124892                        if (!miniCollapse) {
124893                             placeholder.el.slideIn(me.slideDirection[comp.region], {
124894                                 easing: 'linear',
124895                                 duration: 100
124896                             });
124897                         }
124898                         afterCollapse();
124899                     }
124900                 }
124901             });
124902         } else {
124903             compEl.setLeftTop(-10000, -10000);
124904             shadowLayout.layout();
124905             afterCollapse();
124906         }
124907
124908         return false;
124909     },
124910
124911     // Hijack the expand operation to remove the placeholder and slide the region back in.
124912     onBeforeRegionExpand: function(comp, animate) {
124913         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
124914         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
124915         return false;
124916     },
124917
124918     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
124919     onPlaceHolderToolClick: function(e, target, owner, tool) {
124920         var me = this,
124921             comp = tool.client,
124922
124923             // Hide the placeholder unless it was the Component's preexisting splitter
124924             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
124925             compEl = comp.el,
124926             toCompBox,
124927             placeholder = comp.placeholder,
124928             placeholderEl = placeholder.el,
124929             shadowContainer = comp.shadowOwnerCt,
124930             shadowLayout = shadowContainer.layout,
124931             curSize,
124932             sl = me.owner.suspendLayout,
124933             scsl = shadowContainer.suspendLayout,
124934             isFloating;
124935
124936         if (comp.collapsedChangingLayout) {
124937             if (Ext.global.console && Ext.global.console.warn) {
124938                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
124939             }
124940             return false;
124941         }
124942         if (tool.shouldFireBeforeexpand !== false && comp.fireEvent('beforeexpand', comp, true) === false) {
124943             return false;
124944         }
124945         comp.collapsedChangingLayout = true;
124946         // If the slide in is still going, stop it.
124947         // This will either leave the Component in its fully floated state (which is processed below)
124948         // or in its collapsed state. Either way, we expand it..
124949         if (comp.getActiveAnimation()) {
124950             comp.stopAnimation();
124951         }
124952
124953         // If the Component is fully floated when they click the placeholder Tool,
124954         // it will be primed with a slide out animation object... so delete that
124955         // and remove the mouseout listeners
124956         if (comp.slideOutAnim) {
124957             // Remove mouse leave monitors
124958             compEl.un(comp.panelMouseMon);
124959             placeholderEl.un(comp.placeholderMouseMon);
124960
124961             delete comp.slideOutAnim;
124962             delete comp.panelMouseMon;
124963             delete comp.placeholderMouseMon;
124964
124965             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
124966             isFloating = true;
124967         }
124968
124969         // Do not trigger a layout during transition to expanded Component
124970         me.owner.suspendLayout = true;
124971         shadowContainer.suspendLayout = true;
124972
124973         // Prevent upward notifications from downstream layouts
124974         shadowLayout.layoutBusy = true;
124975         if (shadowContainer.componentLayout) {
124976             shadowContainer.componentLayout.layoutBusy = true;
124977         }
124978         me.shadowContainer.layout.layoutBusy = true;
124979         me.layoutBusy = true;
124980         me.owner.componentLayout.layoutBusy = true;
124981
124982         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
124983         // Find where the shadow Box layout plans to put the expanding Component.
124984         comp.hidden = false;
124985         comp.collapsed = false;
124986         if (hidePlaceholder) {
124987             placeholder.hidden = true;
124988         }
124989         toCompBox = shadowLayout.calculateChildBox(comp);
124990
124991         // Show the collapse tool in case it was hidden by the slide-in
124992         if (comp.collapseTool) {
124993             comp.collapseTool.show();
124994         }
124995
124996         // If we're going to animate, we need to hide the component before moving it back into position
124997         if (comp.animCollapse && !isFloating) {
124998             compEl.setStyle('visibility', 'hidden');
124999         }
125000         compEl.setLeftTop(toCompBox.left, toCompBox.top);
125001
125002         // Equalize the size of the expanding Component prior to animation
125003         // in case the layout area has changed size during the time it was collapsed.
125004         curSize = comp.getSize();
125005         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
125006             me.setItemSize(comp, toCompBox.width, toCompBox.height);
125007         }
125008
125009         // Jobs to be done after the expand has been done
125010         function afterExpand() {
125011             // Reinstate automatic laying out.
125012             me.owner.suspendLayout = sl;
125013             shadowContainer.suspendLayout = scsl;
125014             delete shadowLayout.layoutBusy;
125015             if (shadowContainer.componentLayout) {
125016                 delete shadowContainer.componentLayout.layoutBusy;
125017             }
125018             delete me.shadowContainer.layout.layoutBusy;
125019             delete me.layoutBusy;
125020             delete me.owner.componentLayout.layoutBusy;
125021             delete comp.collapsedChangingLayout;
125022
125023             // In case it was floated out and they clicked the re-expand tool
125024             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
125025
125026             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
125027             comp.fireEvent('expand', comp);
125028         }
125029
125030         // Hide the placeholder
125031         if (hidePlaceholder) {
125032             placeholder.el.hide();
125033         }
125034
125035         // Slide the expanding Component to its new position.
125036         // When that is done, layout the layout.
125037         if (comp.animCollapse && !isFloating) {
125038             compEl.dom.style.zIndex = 100;
125039             compEl.slideIn(me.slideDirection[comp.region], {
125040                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
125041                 listeners: {
125042                     afteranimate: function() {
125043                         compEl.dom.style.zIndex = '';
125044                         comp.hidden = false;
125045                         shadowLayout.onLayout();
125046                         afterExpand();
125047                     }
125048                 }
125049             });
125050         } else {
125051             shadowLayout.onLayout();
125052             afterExpand();
125053         }
125054     },
125055
125056     floatCollapsedPanel: function(e, comp) {
125057
125058         if (comp.floatable === false) {
125059             return;
125060         }
125061
125062         var me = this,
125063             compEl = comp.el,
125064             placeholder = comp.placeholder,
125065             placeholderEl = placeholder.el,
125066             shadowContainer = comp.shadowOwnerCt,
125067             shadowLayout = shadowContainer.layout,
125068             placeholderBox = shadowLayout.getChildBox(placeholder),
125069             scsl = shadowContainer.suspendLayout,
125070             curSize, toCompBox, compAnim;
125071
125072         // Ignore clicks on tools.
125073         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
125074             return;
125075         }
125076
125077         // It's *being* animated, ignore the click.
125078         // Possible future enhancement: Stop and *reverse* the current active Fx.
125079         if (compEl.getActiveAnimation()) {
125080             return;
125081         }
125082
125083         // If the Component is already fully floated when they click the placeholder,
125084         // it will be primed with a slide out animation object... so slide it out.
125085         if (comp.slideOutAnim) {
125086             me.slideOutFloatedComponent(comp);
125087             return;
125088         }
125089
125090         // Function to be called when the mouse leaves the floated Panel
125091         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
125092         function onMouseLeaveFloated(e) {
125093             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
125094
125095             // If mouse is not within slide Region, slide it out
125096             if (!slideRegion.contains(e.getPoint())) {
125097                 me.slideOutFloatedComponent(comp);
125098             }
125099         }
125100
125101         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
125102         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
125103
125104         // Do not trigger a layout during slide out of the Component
125105         shadowContainer.suspendLayout = true;
125106
125107         // Prevent upward notifications from downstream layouts
125108         me.layoutBusy = true;
125109         me.owner.componentLayout.layoutBusy = true;
125110
125111         // The collapse tool is hidden while slid.
125112         // It is re-shown on expand.
125113         if (comp.collapseTool) {
125114             comp.collapseTool.hide();
125115         }
125116
125117         // Set flags so that the layout will calculate the boxes for what we want
125118         comp.hidden = false;
125119         comp.collapsed = false;
125120         placeholder.hidden = true;
125121
125122         // Recalculate new arrangement of the Component being floated.
125123         toCompBox = shadowLayout.calculateChildBox(comp);
125124         placeholder.hidden = false;
125125
125126         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
125127         if (comp.region == 'north' || comp.region == 'west') {
125128             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
125129         } else {
125130             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
125131         }
125132         compEl.setStyle('visibility', 'hidden');
125133         compEl.setLeftTop(toCompBox.left, toCompBox.top);
125134
125135         // Equalize the size of the expanding Component prior to animation
125136         // in case the layout area has changed size during the time it was collapsed.
125137         curSize = comp.getSize();
125138         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
125139             me.setItemSize(comp, toCompBox.width, toCompBox.height);
125140         }
125141
125142         // This animation slides the collapsed Component's el out to just beyond its placeholder
125143         compAnim = {
125144             listeners: {
125145                 afteranimate: function() {
125146                     shadowContainer.suspendLayout = scsl;
125147                     delete me.layoutBusy;
125148                     delete me.owner.componentLayout.layoutBusy;
125149
125150                     // Prime the Component with an Anim config object to slide it back out
125151                     compAnim.listeners = {
125152                         afterAnimate: function() {
125153                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
125154
125155                             // Reinstate the correct, current state after slide out animation finishes
125156                             comp.hidden = true;
125157                             comp.collapsed = true;
125158                             delete comp.slideOutAnim;
125159                             delete comp.panelMouseMon;
125160                             delete comp.placeholderMouseMon;
125161                         }
125162                     };
125163                     comp.slideOutAnim = compAnim;
125164                 }
125165             },
125166             duration: 500
125167         };
125168
125169         // Give the element the correct class which places it at a high z-index
125170         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
125171
125172         // Begin the slide in
125173         compEl.slideIn(me.slideDirection[comp.region], compAnim);
125174
125175         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
125176         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
125177
125178     },
125179
125180     slideOutFloatedComponent: function(comp) {
125181         var compEl = comp.el,
125182             slideOutAnim;
125183
125184         // Remove mouse leave monitors
125185         compEl.un(comp.panelMouseMon);
125186         comp.placeholder.el.un(comp.placeholderMouseMon);
125187
125188         // Slide the Component out
125189         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
125190
125191         delete comp.slideOutAnim;
125192         delete comp.panelMouseMon;
125193         delete comp.placeholderMouseMon;
125194     },
125195
125196     /*
125197      * @private
125198      * Ensure any collapsed placeholder Component is destroyed along with its region.
125199      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
125200      */
125201     onRegionDestroy: function(comp) {
125202         var placeholder = comp.placeholder;
125203         if (placeholder) {
125204             delete placeholder.ownerCt;
125205             placeholder.destroy();
125206         }
125207     },
125208
125209     /*
125210      * @private
125211      * Ensure any shadow Containers are destroyed.
125212      * Ensure we don't keep references to Components.
125213      */
125214     onDestroy: function() {
125215         var me = this,
125216             shadowContainer = me.shadowContainer,
125217             embeddedContainer = me.embeddedContainer;
125218
125219         if (shadowContainer) {
125220             delete shadowContainer.ownerCt;
125221             Ext.destroy(shadowContainer);
125222         }
125223
125224         if (embeddedContainer) {
125225             delete embeddedContainer.ownerCt;
125226             Ext.destroy(embeddedContainer);
125227         }
125228         delete me.regions;
125229         delete me.splitters;
125230         delete me.shadowContainer;
125231         delete me.embeddedContainer;
125232         me.callParent(arguments);
125233     }
125234 });
125235
125236 /**
125237  * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
125238  * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
125239  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
125240  * and should generally not need to be created directly via the new keyword.
125241  *
125242  * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
125243  * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
125244  * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
125245  * so that functionality must be provided by the developer.
125246  *
125247  * To change the active card of a container, call the setActiveItem method of its layout:
125248  *
125249  *     Ext.create('Ext.panel.Panel', {
125250  *         layout: 'card',
125251  *         items: [
125252  *             { html: 'Card 1' },
125253  *             { html: 'Card 2' }
125254  *         ],
125255  *         renderTo: Ext.getBody()
125256  *     });
125257  *
125258  *     p.getLayout().setActiveItem(1);
125259  *
125260  * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
125261  * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
125262  * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
125263  * completely different implementation.  For serious implementations, a better approach would be to extend
125264  * CardLayout to provide the custom functionality needed.
125265  *
125266  *     @example
125267  *     var navigate = function(panel, direction){
125268  *         // This routine could contain business logic required to manage the navigation steps.
125269  *         // It would call setActiveItem as needed, manage navigation button state, handle any
125270  *         // branching logic that might be required, handle alternate actions like cancellation
125271  *         // or finalization, etc.  A complete wizard implementation could get pretty
125272  *         // sophisticated depending on the complexity required, and should probably be
125273  *         // done as a subclass of CardLayout in a real-world implementation.
125274  *         var layout = panel.getLayout();
125275  *         layout[direction]();
125276  *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
125277  *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
125278  *     };
125279  *
125280  *     Ext.create('Ext.panel.Panel', {
125281  *         title: 'Example Wizard',
125282  *         width: 300,
125283  *         height: 200,
125284  *         layout: 'card',
125285  *         bodyStyle: 'padding:15px',
125286  *         defaults: {
125287  *             // applied to each contained panel
125288  *             border: false
125289  *         },
125290  *         // just an example of one possible navigation scheme, using buttons
125291  *         bbar: [
125292  *             {
125293  *                 id: 'move-prev',
125294  *                 text: 'Back',
125295  *                 handler: function(btn) {
125296  *                     navigate(btn.up("panel"), "prev");
125297  *                 },
125298  *                 disabled: true
125299  *             },
125300  *             '->', // greedy spacer so that the buttons are aligned to each side
125301  *             {
125302  *                 id: 'move-next',
125303  *                 text: 'Next',
125304  *                 handler: function(btn) {
125305  *                     navigate(btn.up("panel"), "next");
125306  *                 }
125307  *             }
125308  *         ],
125309  *         // the panels (or "cards") within the layout
125310  *         items: [{
125311  *             id: 'card-0',
125312  *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
125313  *         },{
125314  *             id: 'card-1',
125315  *             html: '<p>Step 2 of 3</p>'
125316  *         },{
125317  *             id: 'card-2',
125318  *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
125319  *         }],
125320  *         renderTo: Ext.getBody()
125321  *     });
125322  */
125323 Ext.define('Ext.layout.container.Card', {
125324
125325     /* Begin Definitions */
125326
125327     alias: ['layout.card'],
125328     alternateClassName: 'Ext.layout.CardLayout',
125329
125330     extend: 'Ext.layout.container.AbstractCard',
125331
125332     /* End Definitions */
125333
125334     /**
125335      * Makes the given card active.
125336      *
125337      *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
125338      *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
125339      *     var panel = Ext.create('Ext.panel.Panel', {
125340      *         layout: 'card',
125341      *         activeItem: 0,
125342      *         items: [card1, card2]
125343      *     });
125344      *     // These are all equivalent
125345      *     panel.getLayout().setActiveItem(card2);
125346      *     panel.getLayout().setActiveItem('card-2');
125347      *     panel.getLayout().setActiveItem(1);
125348      *
125349      * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
125350      * {@link Ext.Component#itemId itemId}, or index of component.
125351      * @return {Ext.Component} the activated component or false when nothing activated.
125352      * False is returned also when trying to activate an already active card.
125353      */
125354     setActiveItem: function(newCard) {
125355         var me = this,
125356             owner = me.owner,
125357             oldCard = me.activeItem,
125358             newIndex;
125359
125360         newCard = me.parseActiveItem(newCard);
125361         newIndex = owner.items.indexOf(newCard);
125362
125363         // If the card is not a child of the owner, then add it
125364         if (newIndex == -1) {
125365             newIndex = owner.items.items.length;
125366             owner.add(newCard);
125367         }
125368
125369         // Is this a valid, different card?
125370         if (newCard && oldCard != newCard) {
125371             // If the card has not been rendered yet, now is the time to do so.
125372             if (!newCard.rendered) {
125373                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
125374                 me.configureItem(newCard, 0);
125375             }
125376
125377             me.activeItem = newCard;
125378
125379             // Fire the beforeactivate and beforedeactivate events on the cards
125380             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
125381                 return false;
125382             }
125383             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
125384                 return false;
125385             }
125386
125387             // If the card hasnt been sized yet, do it now
125388             if (me.sizeAllCards) {
125389                 // onLayout calls setItemBox
125390                 me.onLayout();
125391             }
125392             else {
125393                 me.setItemBox(newCard, me.getTargetBox());
125394             }
125395
125396             me.owner.suspendLayout = true;
125397
125398             if (oldCard) {
125399                 if (me.hideInactive) {
125400                     oldCard.hide();
125401                 }
125402                 oldCard.fireEvent('deactivate', oldCard, newCard);
125403             }
125404
125405             // Make sure the new card is shown
125406             me.owner.suspendLayout = false;
125407             if (newCard.hidden) {
125408                 newCard.show();
125409             } else {
125410                 me.onLayout();
125411             }
125412
125413             newCard.fireEvent('activate', newCard, oldCard);
125414
125415             return newCard;
125416         }
125417         return false;
125418     },
125419
125420     configureItem: function(item) {
125421         // Card layout only controls dimensions which IT has controlled.
125422         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
125423         item.layoutManagedHeight = 0;
125424         item.layoutManagedWidth = 0;
125425
125426         this.callParent(arguments);
125427     }});
125428 /**
125429  * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
125430  * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
125431  * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
125432  * and should generally not need to be created directly via the new keyword.
125433  *
125434  * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
125435  * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
125436  * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
125437  * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
125438  *
125439  * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
125440  * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
125441  *
125442  * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
125443  * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
125444  * are skipped, but their widths are subtracted from the overall container width.
125445  *
125446  * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
125447  * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
125448  * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
125449  * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
125450  * together, otherwise your layout may not render as expected.
125451  *
125452  *     @example
125453  *     // All columns are percentages -- they must add up to 1
125454  *     Ext.create('Ext.panel.Panel', {
125455  *         title: 'Column Layout - Percentage Only',
125456  *         width: 350,
125457  *         height: 250,
125458  *         layout:'column',
125459  *         items: [{
125460  *             title: 'Column 1',
125461  *             columnWidth: .25
125462  *         },{
125463  *             title: 'Column 2',
125464  *             columnWidth: .55
125465  *         },{
125466  *             title: 'Column 3',
125467  *             columnWidth: .20
125468  *         }],
125469  *         renderTo: Ext.getBody()
125470  *     });
125471  *
125472  *     // Mix of width and columnWidth -- all columnWidth values must add up
125473  *     // to 1. The first column will take up exactly 120px, and the last two
125474  *     // columns will fill the remaining container width.
125475  *
125476  *     Ext.create('Ext.Panel', {
125477  *         title: 'Column Layout - Mixed',
125478  *         width: 350,
125479  *         height: 250,
125480  *         layout:'column',
125481  *         items: [{
125482  *             title: 'Column 1',
125483  *             width: 120
125484  *         },{
125485  *             title: 'Column 2',
125486  *             columnWidth: .7
125487  *         },{
125488  *             title: 'Column 3',
125489  *             columnWidth: .3
125490  *         }],
125491  *         renderTo: Ext.getBody()
125492  *     });
125493  */
125494 Ext.define('Ext.layout.container.Column', {
125495
125496     extend: 'Ext.layout.container.Auto',
125497     alias: ['layout.column'],
125498     alternateClassName: 'Ext.layout.ColumnLayout',
125499
125500     type: 'column',
125501
125502     itemCls: Ext.baseCSSPrefix + 'column',
125503
125504     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
125505
125506     scrollOffset: 0,
125507
125508     bindToOwnerCtComponent: false,
125509
125510     getRenderTarget : function() {
125511         if (!this.innerCt) {
125512
125513             // the innerCt prevents wrapping and shuffling while
125514             // the container is resizing
125515             this.innerCt = this.getTarget().createChild({
125516                 cls: Ext.baseCSSPrefix + 'column-inner'
125517             });
125518
125519             // Column layout uses natural HTML flow to arrange the child items.
125520             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
125521             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
125522             this.clearEl = this.innerCt.createChild({
125523                 cls: Ext.baseCSSPrefix + 'clear',
125524                 role: 'presentation'
125525             });
125526         }
125527         return this.innerCt;
125528     },
125529
125530     // private
125531     onLayout : function() {
125532         var me = this,
125533             target = me.getTarget(),
125534             items = me.getLayoutItems(),
125535             len = items.length,
125536             item,
125537             i,
125538             parallelMargins = [],
125539             itemParallelMargins,
125540             size,
125541             availableWidth,
125542             columnWidth;
125543
125544         size = me.getLayoutTargetSize();
125545         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
125546             return;
125547         }
125548
125549         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
125550         // If, after the layout, it is detected that there is vertical overflow,
125551         // we will recurse back through here. Do not adjust overflow style at that time.
125552         if (me.adjustmentPass) {
125553             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
125554                 size.width = me.adjustedWidth;
125555             }
125556         } else {
125557             i = target.getStyle('overflow');
125558             if (i && i != 'hidden') {
125559                 me.autoScroll = true;
125560                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
125561                     target.setStyle('overflow', 'hidden');
125562                     size = me.getLayoutTargetSize();
125563                 }
125564             }
125565         }
125566
125567         availableWidth = size.width - me.scrollOffset;
125568         me.innerCt.setWidth(availableWidth);
125569
125570         // some columns can be percentages while others are fixed
125571         // so we need to make 2 passes
125572         for (i = 0; i < len; i++) {
125573             item = items[i];
125574             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
125575             if (!item.columnWidth) {
125576                 availableWidth -= (item.getWidth() + itemParallelMargins);
125577             }
125578         }
125579
125580         availableWidth = availableWidth < 0 ? 0 : availableWidth;
125581         for (i = 0; i < len; i++) {
125582             item = items[i];
125583             if (item.columnWidth) {
125584                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
125585                 me.setItemSize(item, columnWidth, item.height);
125586             } else {
125587                 me.layoutItem(item);
125588             }
125589         }
125590
125591         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
125592         if (!me.adjustmentPass && me.autoScroll) {
125593
125594             // If there's a vertical overflow, relay with scrollbars
125595             target.setStyle('overflow', 'auto');
125596             me.adjustmentPass = (target.dom.scrollHeight > size.height);
125597             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
125598                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
125599             } else {
125600                 target.setStyle('overflow', 'auto');
125601             }
125602
125603             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
125604             if (me.adjustmentPass) {
125605                 me.onLayout();
125606             }
125607         }
125608         delete me.adjustmentPass;
125609     },
125610
125611     configureItem: function(item) {
125612         this.callParent(arguments);
125613
125614         if (item.columnWidth) {
125615             item.layoutManagedWidth = 1;
125616         }
125617     }
125618 });
125619 /**
125620  * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
125621  * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
125622  * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
125623  * need to be created directly via the new keyword.
125624  *
125625  * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
125626  * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
125627  * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
125628  * added to a TableLayout can supply the following table-specific config properties:
125629  *
125630  *   - **rowspan** Applied to the table cell containing the item.
125631  *   - **colspan** Applied to the table cell containing the item.
125632  *   - **cellId** An id applied to the table cell containing the item.
125633  *   - **cellCls** A CSS class name added to the table cell containing the item.
125634  *
125635  * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
125636  * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
125637  * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
125638  * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
125639  * layoutConfig and start adding panels in their natural order from left to right, top to bottom. The layout will
125640  * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
125641  * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
125642  * end up with missing and/or extra cells! Example usage:
125643  *
125644  *     @example
125645  *     Ext.create('Ext.panel.Panel', {
125646  *         title: 'Table Layout',
125647  *         width: 300,
125648  *         height: 150,
125649  *         layout: {
125650  *             type: 'table',
125651  *             // The total column count must be specified here
125652  *             columns: 3
125653  *         },
125654  *         defaults: {
125655  *             // applied to each contained panel
125656  *             bodyStyle: 'padding:20px'
125657  *         },
125658  *         items: [{
125659  *             html: 'Cell A content',
125660  *             rowspan: 2
125661  *         },{
125662  *             html: 'Cell B content',
125663  *             colspan: 2
125664  *         },{
125665  *             html: 'Cell C content',
125666  *             cellCls: 'highlight'
125667  *         },{
125668  *             html: 'Cell D content'
125669  *         }],
125670  *         renderTo: Ext.getBody()
125671  *     });
125672  */
125673 Ext.define('Ext.layout.container.Table', {
125674
125675     /* Begin Definitions */
125676
125677     alias: ['layout.table'],
125678     extend: 'Ext.layout.container.Auto',
125679     alternateClassName: 'Ext.layout.TableLayout',
125680
125681     /* End Definitions */
125682
125683     /**
125684      * @cfg {Number} columns
125685      * The total number of columns to create in the table for this layout. If not specified, all Components added to
125686      * this layout will be rendered into a single row using one column per Component.
125687      */
125688
125689     // private
125690     monitorResize:false,
125691
125692     type: 'table',
125693
125694     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
125695     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
125696     autoSize: true,
125697
125698     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
125699
125700     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
125701     tableCls: Ext.baseCSSPrefix + 'table-layout',
125702     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
125703
125704     /**
125705      * @cfg {Object} tableAttrs
125706      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
125707      * create the layout's `<table>` element. Example:
125708      *
125709      *     {
125710      *         xtype: 'panel',
125711      *         layout: {
125712      *             type: 'table',
125713      *             columns: 3,
125714      *             tableAttrs: {
125715      *                 style: {
125716      *                     width: '100%'
125717      *                 }
125718      *             }
125719      *         }
125720      *     }
125721      */
125722     tableAttrs:null,
125723
125724     /**
125725      * @cfg {Object} trAttrs
125726      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
125727      * create the layout's <tr> elements.
125728      */
125729
125730     /**
125731      * @cfg {Object} tdAttrs
125732      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
125733      * create the layout's <td> elements.
125734      */
125735
125736     /**
125737      * @private
125738      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
125739      * location in the table structure.
125740      */
125741     renderItems: function(items) {
125742         var tbody = this.getTable().tBodies[0],
125743             rows = tbody.rows,
125744             i = 0,
125745             len = items.length,
125746             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
125747
125748         // Calculate the correct cell structure for the current items
125749         cells = this.calculateCells(items);
125750
125751         // Loop over each cell and compare to the current cells in the table, inserting/
125752         // removing/moving cells as needed, and making sure each item is rendered into
125753         // the correct cell.
125754         for (; i < len; i++) {
125755             curCell = cells[i];
125756             rowIdx = curCell.rowIdx;
125757             cellIdx = curCell.cellIdx;
125758             item = items[i];
125759
125760             // If no row present, create and insert one
125761             trEl = rows[rowIdx];
125762             if (!trEl) {
125763                 trEl = tbody.insertRow(rowIdx);
125764                 if (this.trAttrs) {
125765                     trEl.set(this.trAttrs);
125766                 }
125767             }
125768
125769             // If no cell present, create and insert one
125770             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
125771             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
125772                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
125773                 itemCt.setWidth(null);
125774             }
125775
125776             // Render or move the component into the cell
125777             if (!item.rendered) {
125778                 this.renderItem(item, itemCt, 0);
125779             }
125780             else if (!this.isValidParent(item, itemCt, 0)) {
125781                 this.moveItem(item, itemCt, 0);
125782             }
125783
125784             // Set the cell properties
125785             if (this.tdAttrs) {
125786                 tdEl.set(this.tdAttrs);
125787             }
125788             tdEl.set({
125789                 colSpan: item.colspan || 1,
125790                 rowSpan: item.rowspan || 1,
125791                 id: item.cellId || '',
125792                 cls: this.cellCls + ' ' + (item.cellCls || '')
125793             });
125794
125795             // If at the end of a row, remove any extra cells
125796             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
125797                 cellIdx++;
125798                 while (trEl.cells[cellIdx]) {
125799                     trEl.deleteCell(cellIdx);
125800                 }
125801             }
125802         }
125803
125804         // Delete any extra rows
125805         rowIdx++;
125806         while (tbody.rows[rowIdx]) {
125807             tbody.deleteRow(rowIdx);
125808         }
125809     },
125810
125811     afterLayout: function() {
125812         this.callParent();
125813
125814         if (this.needsDivWrap()) {
125815             // set wrapper div width to match layed out item - see docs below
125816             Ext.Array.forEach(this.getLayoutItems(), function(item) {
125817                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
125818             });
125819         }
125820     },
125821
125822     /**
125823      * @private
125824      * Determine the row and cell indexes for each component, taking into consideration
125825      * the number of columns and each item's configured colspan/rowspan values.
125826      * @param {Array} items The layout components
125827      * @return {Object[]} List of row and cell indexes for each of the components
125828      */
125829     calculateCells: function(items) {
125830         var cells = [],
125831             rowIdx = 0,
125832             colIdx = 0,
125833             cellIdx = 0,
125834             totalCols = this.columns || Infinity,
125835             rowspans = [], //rolling list of active rowspans for each column
125836             i = 0, j,
125837             len = items.length,
125838             item;
125839
125840         for (; i < len; i++) {
125841             item = items[i];
125842
125843             // Find the first available row/col slot not taken up by a spanning cell
125844             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
125845                 if (colIdx >= totalCols) {
125846                     // move down to next row
125847                     colIdx = 0;
125848                     cellIdx = 0;
125849                     rowIdx++;
125850
125851                     // decrement all rowspans
125852                     for (j = 0; j < totalCols; j++) {
125853                         if (rowspans[j] > 0) {
125854                             rowspans[j]--;
125855                         }
125856                     }
125857                 } else {
125858                     colIdx++;
125859                 }
125860             }
125861
125862             // Add the cell info to the list
125863             cells.push({
125864                 rowIdx: rowIdx,
125865                 cellIdx: cellIdx
125866             });
125867
125868             // Increment
125869             for (j = item.colspan || 1; j; --j) {
125870                 rowspans[colIdx] = item.rowspan || 1;
125871                 ++colIdx;
125872             }
125873             ++cellIdx;
125874         }
125875
125876         return cells;
125877     },
125878
125879     /**
125880      * @private
125881      * Return the layout's table element, creating it if necessary.
125882      */
125883     getTable: function() {
125884         var table = this.table;
125885         if (!table) {
125886             table = this.table = this.getTarget().createChild(
125887                 Ext.apply({
125888                     tag: 'table',
125889                     role: 'presentation',
125890                     cls: this.tableCls,
125891                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
125892                     cn: {tag: 'tbody'}
125893                 }, this.tableAttrs),
125894                 null, true
125895             );
125896         }
125897         return table;
125898     },
125899
125900     /**
125901      * @private
125902      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
125903      * will include that padding in the size of the cell, making it always larger than the
125904      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
125905      * and then set that div's width to match the item rendered within it afterLayout. This method
125906      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
125907      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
125908      */
125909     needsDivWrap: function() {
125910         return Ext.isOpera10_5;
125911     }
125912 });
125913 /**
125914  * A base class for all menu items that require menu-related functionality such as click handling,
125915  * sub-menus, icons, etc.
125916  *
125917  *     @example
125918  *     Ext.create('Ext.menu.Menu', {
125919  *         width: 100,
125920  *         height: 100,
125921  *         floating: false,  // usually you want this set to True (default)
125922  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125923  *         items: [{
125924  *             text: 'icon item',
125925  *             iconCls: 'add16'
125926  *         },{
125927  *             text: 'text item'
125928  *         },{
125929  *             text: 'plain item',
125930  *             plain: true
125931  *         }]
125932  *     });
125933  */
125934 Ext.define('Ext.menu.Item', {
125935     extend: 'Ext.Component',
125936     alias: 'widget.menuitem',
125937     alternateClassName: 'Ext.menu.TextItem',
125938
125939     /**
125940      * @property {Boolean} activated
125941      * Whether or not this item is currently activated
125942      */
125943
125944     /**
125945      * @property {Ext.menu.Menu} parentMenu
125946      * The parent Menu of this item.
125947      */
125948
125949     /**
125950      * @cfg {String} activeCls
125951      * The CSS class added to the menu item when the item is activated (focused/mouseover).
125952      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
125953      */
125954     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
125955
125956     /**
125957      * @cfg {String} ariaRole @hide
125958      */
125959     ariaRole: 'menuitem',
125960
125961     /**
125962      * @cfg {Boolean} canActivate
125963      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
125964      */
125965     canActivate: true,
125966
125967     /**
125968      * @cfg {Number} clickHideDelay
125969      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
125970      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
125971      */
125972     clickHideDelay: 1,
125973
125974     /**
125975      * @cfg {Boolean} destroyMenu
125976      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
125977      */
125978     destroyMenu: true,
125979
125980     /**
125981      * @cfg {String} disabledCls
125982      * The CSS class added to the menu item when the item is disabled.
125983      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
125984      */
125985     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
125986
125987     /**
125988      * @cfg {String} href
125989      * The href attribute to use for the underlying anchor link. Defaults to `#`.
125990      * @markdown
125991      */
125992
125993      /**
125994       * @cfg {String} hrefTarget
125995       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
125996       * @markdown
125997       */
125998
125999     /**
126000      * @cfg {Boolean} hideOnClick
126001      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
126002      * @markdown
126003      */
126004     hideOnClick: true,
126005
126006     /**
126007      * @cfg {String} icon
126008      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
126009      * @markdown
126010      */
126011
126012     /**
126013      * @cfg {String} iconCls
126014      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
126015      * @markdown
126016      */
126017
126018     isMenuItem: true,
126019
126020     /**
126021      * @cfg {Mixed} menu
126022      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
126023      * which will act as a sub-menu to this item.
126024      * @markdown
126025      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
126026      */
126027
126028     /**
126029      * @cfg {String} menuAlign
126030      * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
126031      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
126032      * @markdown
126033      */
126034     menuAlign: 'tl-tr?',
126035
126036     /**
126037      * @cfg {Number} menuExpandDelay
126038      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
126039      * @markdown
126040      */
126041     menuExpandDelay: 200,
126042
126043     /**
126044      * @cfg {Number} menuHideDelay
126045      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
126046      * @markdown
126047      */
126048     menuHideDelay: 200,
126049
126050     /**
126051      * @cfg {Boolean} plain
126052      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
126053      * @markdown
126054      */
126055
126056     renderTpl: [
126057         '<tpl if="plain">',
126058             '{text}',
126059         '</tpl>',
126060         '<tpl if="!plain">',
126061             '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
126062                 '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
126063                 '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
126064                 '<tpl if="menu">',
126065                     '<img id="{id}-arrowEl" src="{blank}" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
126066                 '</tpl>',
126067             '</a>',
126068         '</tpl>'
126069     ],
126070
126071     maskOnDisable: false,
126072
126073     /**
126074      * @cfg {String} text
126075      * The text/html to display in this item. Defaults to `undefined`.
126076      * @markdown
126077      */
126078
126079     activate: function() {
126080         var me = this;
126081
126082         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
126083             me.el.addCls(me.activeCls);
126084             me.focus();
126085             me.activated = true;
126086             me.fireEvent('activate', me);
126087         }
126088     },
126089
126090     blur: function() {
126091         this.$focused = false;
126092         this.callParent(arguments);
126093     },
126094
126095     deactivate: function() {
126096         var me = this;
126097
126098         if (me.activated) {
126099             me.el.removeCls(me.activeCls);
126100             me.blur();
126101             me.hideMenu();
126102             me.activated = false;
126103             me.fireEvent('deactivate', me);
126104         }
126105     },
126106
126107     deferExpandMenu: function() {
126108         var me = this;
126109
126110         if (!me.menu.rendered || !me.menu.isVisible()) {
126111             me.parentMenu.activeChild = me.menu;
126112             me.menu.parentItem = me;
126113             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
126114             me.menu.showBy(me, me.menuAlign);
126115         }
126116     },
126117
126118     deferHideMenu: function() {
126119         if (this.menu.isVisible()) {
126120             this.menu.hide();
126121         }
126122     },
126123
126124     deferHideParentMenus: function() {
126125         Ext.menu.Manager.hideAll();
126126     },
126127
126128     expandMenu: function(delay) {
126129         var me = this;
126130
126131         if (me.menu) {
126132             clearTimeout(me.hideMenuTimer);
126133             if (delay === 0) {
126134                 me.deferExpandMenu();
126135             } else {
126136                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
126137             }
126138         }
126139     },
126140
126141     focus: function() {
126142         this.$focused = true;
126143         this.callParent(arguments);
126144     },
126145
126146     getRefItems: function(deep){
126147         var menu = this.menu,
126148             items;
126149
126150         if (menu) {
126151             items = menu.getRefItems(deep);
126152             items.unshift(menu);
126153         }
126154         return items || [];
126155     },
126156
126157     hideMenu: function(delay) {
126158         var me = this;
126159
126160         if (me.menu) {
126161             clearTimeout(me.expandMenuTimer);
126162             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
126163         }
126164     },
126165
126166     initComponent: function() {
126167         var me = this,
126168             prefix = Ext.baseCSSPrefix,
126169             cls = [prefix + 'menu-item'];
126170
126171         me.addEvents(
126172             /**
126173              * @event activate
126174              * Fires when this item is activated
126175              * @param {Ext.menu.Item} item The activated item
126176              */
126177             'activate',
126178
126179             /**
126180              * @event click
126181              * Fires when this item is clicked
126182              * @param {Ext.menu.Item} item The item that was clicked
126183              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
126184              */
126185             'click',
126186
126187             /**
126188              * @event deactivate
126189              * Fires when this tiem is deactivated
126190              * @param {Ext.menu.Item} item The deactivated item
126191              */
126192             'deactivate'
126193         );
126194
126195         if (me.plain) {
126196             cls.push(prefix + 'menu-item-plain');
126197         }
126198
126199         if (me.cls) {
126200             cls.push(me.cls);
126201         }
126202
126203         me.cls = cls.join(' ');
126204
126205         if (me.menu) {
126206             me.menu = Ext.menu.Manager.get(me.menu);
126207         }
126208
126209         me.callParent(arguments);
126210     },
126211
126212     onClick: function(e) {
126213         var me = this;
126214
126215         if (!me.href) {
126216             e.stopEvent();
126217         }
126218
126219         if (me.disabled) {
126220             return;
126221         }
126222
126223         if (me.hideOnClick) {
126224             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
126225         }
126226
126227         Ext.callback(me.handler, me.scope || me, [me, e]);
126228         me.fireEvent('click', me, e);
126229
126230         if (!me.hideOnClick) {
126231             me.focus();
126232         }
126233     },
126234
126235     onDestroy: function() {
126236         var me = this;
126237
126238         clearTimeout(me.expandMenuTimer);
126239         clearTimeout(me.hideMenuTimer);
126240         clearTimeout(me.deferHideParentMenusTimer);
126241
126242         if (me.menu) {
126243             delete me.menu.parentItem;
126244             delete me.menu.parentMenu;
126245             delete me.menu.ownerCt;
126246             if (me.destroyMenu !== false) {
126247                 me.menu.destroy();
126248             }
126249         }
126250         me.callParent(arguments);
126251     },
126252
126253     onRender: function(ct, pos) {
126254         var me = this,
126255             blank = Ext.BLANK_IMAGE_URL;
126256
126257         Ext.applyIf(me.renderData, {
126258             href: me.href || '#',
126259             hrefTarget: me.hrefTarget,
126260             icon: me.icon || blank,
126261             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
126262             menu: Ext.isDefined(me.menu),
126263             plain: me.plain,
126264             text: me.text,
126265             blank: blank
126266         });
126267
126268         me.addChildEls('itemEl', 'iconEl', 'textEl', 'arrowEl');
126269
126270         me.callParent(arguments);
126271     },
126272
126273     /**
126274      * Sets the {@link #click} handler of this item
126275      * @param {Function} fn The handler function
126276      * @param {Object} scope (optional) The scope of the handler function
126277      */
126278     setHandler: function(fn, scope) {
126279         this.handler = fn || null;
126280         this.scope = scope;
126281     },
126282
126283     /**
126284      * Sets the {@link #iconCls} of this item
126285      * @param {String} iconCls The CSS class to set to {@link #iconCls}
126286      */
126287     setIconCls: function(iconCls) {
126288         var me = this;
126289
126290         if (me.iconEl) {
126291             if (me.iconCls) {
126292                 me.iconEl.removeCls(me.iconCls);
126293             }
126294
126295             if (iconCls) {
126296                 me.iconEl.addCls(iconCls);
126297             }
126298         }
126299
126300         me.iconCls = iconCls;
126301     },
126302
126303     /**
126304      * Sets the {@link #text} of this item
126305      * @param {String} text The {@link #text}
126306      */
126307     setText: function(text) {
126308         var me = this,
126309             el = me.textEl || me.el;
126310
126311         me.text = text;
126312
126313         if (me.rendered) {
126314             el.update(text || '');
126315             // cannot just call doComponentLayout due to stretchmax
126316             me.ownerCt.redoComponentLayout();
126317         }
126318     }
126319 });
126320
126321 /**
126322  * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
126323  *
126324  *     @example
126325  *     Ext.create('Ext.menu.Menu', {
126326  *         width: 100,
126327  *         height: 110,
126328  *         floating: false,  // usually you want this set to True (default)
126329  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126330  *         items: [{
126331  *             xtype: 'menucheckitem',
126332  *             text: 'select all'
126333  *         },{
126334  *             xtype: 'menucheckitem',
126335  *             text: 'select specific',
126336  *         },{
126337  *             iconCls: 'add16',
126338  *             text: 'icon item'
126339  *         },{
126340  *             text: 'regular item'
126341  *         }]
126342  *     });
126343  */
126344 Ext.define('Ext.menu.CheckItem', {
126345     extend: 'Ext.menu.Item',
126346     alias: 'widget.menucheckitem',
126347
126348     /**
126349      * @cfg {String} checkedCls
126350      * The CSS class used by {@link #cls} to show the checked state.
126351      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
126352      */
126353     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
126354     /**
126355      * @cfg {String} uncheckedCls
126356      * The CSS class used by {@link #cls} to show the unchecked state.
126357      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
126358      */
126359     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
126360     /**
126361      * @cfg {String} groupCls
126362      * The CSS class applied to this item's icon image to denote being a part of a radio group.
126363      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
126364      * Any specified {@link #iconCls} overrides this.
126365      */
126366     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
126367
126368     /**
126369      * @cfg {Boolean} hideOnClick
126370      * Whether to not to hide the owning menu when this item is clicked.
126371      * Defaults to `false` for checkbox items, and to `true` for radio group items.
126372      */
126373     hideOnClick: false,
126374
126375     afterRender: function() {
126376         var me = this;
126377         this.callParent();
126378         me.checked = !me.checked;
126379         me.setChecked(!me.checked, true);
126380     },
126381
126382     initComponent: function() {
126383         var me = this;
126384         me.addEvents(
126385             /**
126386              * @event beforecheckchange
126387              * Fires before a change event. Return false to cancel.
126388              * @param {Ext.menu.CheckItem} this
126389              * @param {Boolean} checked
126390              */
126391             'beforecheckchange',
126392
126393             /**
126394              * @event checkchange
126395              * Fires after a change event.
126396              * @param {Ext.menu.CheckItem} this
126397              * @param {Boolean} checked
126398              */
126399             'checkchange'
126400         );
126401
126402         me.callParent(arguments);
126403
126404         Ext.menu.Manager.registerCheckable(me);
126405
126406         if (me.group) {
126407             if (!me.iconCls) {
126408                 me.iconCls = me.groupCls;
126409             }
126410             if (me.initialConfig.hideOnClick !== false) {
126411                 me.hideOnClick = true;
126412             }
126413         }
126414     },
126415
126416     /**
126417      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
126418      * will still be accessible
126419      */
126420     disableCheckChange: function() {
126421         var me = this;
126422
126423         if (me.iconEl) {
126424             me.iconEl.addCls(me.disabledCls);
126425         }
126426         me.checkChangeDisabled = true;
126427     },
126428
126429     /**
126430      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
126431      */
126432     enableCheckChange: function() {
126433         var me = this;
126434
126435         me.iconEl.removeCls(me.disabledCls);
126436         me.checkChangeDisabled = false;
126437     },
126438
126439     onClick: function(e) {
126440         var me = this;
126441         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
126442             me.setChecked(!me.checked);
126443         }
126444         this.callParent([e]);
126445     },
126446
126447     onDestroy: function() {
126448         Ext.menu.Manager.unregisterCheckable(this);
126449         this.callParent(arguments);
126450     },
126451
126452     /**
126453      * Sets the checked state of the item
126454      * @param {Boolean} checked True to check, false to uncheck
126455      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
126456      */
126457     setChecked: function(checked, suppressEvents) {
126458         var me = this;
126459         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
126460             if (me.el) {
126461                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
126462             }
126463             me.checked = checked;
126464             Ext.menu.Manager.onCheckChange(me, checked);
126465             if (!suppressEvents) {
126466                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
126467                 me.fireEvent('checkchange', me, checked);
126468             }
126469         }
126470     }
126471 });
126472
126473 /**
126474  * @class Ext.menu.KeyNav
126475  * @private
126476  */
126477 Ext.define('Ext.menu.KeyNav', {
126478     extend: 'Ext.util.KeyNav',
126479
126480     requires: ['Ext.FocusManager'],
126481     
126482     constructor: function(menu) {
126483         var me = this;
126484
126485         me.menu = menu;
126486         me.callParent([menu.el, {
126487             down: me.down,
126488             enter: me.enter,
126489             esc: me.escape,
126490             left: me.left,
126491             right: me.right,
126492             space: me.enter,
126493             tab: me.tab,
126494             up: me.up
126495         }]);
126496     },
126497
126498     down: function(e) {
126499         var me = this,
126500             fi = me.menu.focusedItem;
126501
126502         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
126503             return true;
126504         }
126505         me.focusNextItem(1);
126506     },
126507
126508     enter: function(e) {
126509         var menu = this.menu,
126510             focused = menu.focusedItem;
126511  
126512         if (menu.activeItem) {
126513             menu.onClick(e);
126514         } else if (focused && focused.isFormField) {
126515             // prevent stopEvent being called
126516             return true;
126517         }
126518     },
126519
126520     escape: function(e) {
126521         Ext.menu.Manager.hideAll();
126522     },
126523
126524     focusNextItem: function(step) {
126525         var menu = this.menu,
126526             items = menu.items,
126527             focusedItem = menu.focusedItem,
126528             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
126529             idx = startIdx + step;
126530
126531         while (idx != startIdx) {
126532             if (idx < 0) {
126533                 idx = items.length - 1;
126534             } else if (idx >= items.length) {
126535                 idx = 0;
126536             }
126537
126538             var item = items.getAt(idx);
126539             if (menu.canActivateItem(item)) {
126540                 menu.setActiveItem(item);
126541                 break;
126542             }
126543             idx += step;
126544         }
126545     },
126546
126547     isWhitelisted: function(item) {
126548         return Ext.FocusManager.isWhitelisted(item);
126549     },
126550
126551     left: function(e) {
126552         var menu = this.menu,
126553             fi = menu.focusedItem,
126554             ai = menu.activeItem;
126555
126556         if (fi && this.isWhitelisted(fi)) {
126557             return true;
126558         }
126559
126560         menu.hide();
126561         if (menu.parentMenu) {
126562             menu.parentMenu.focus();
126563         }
126564     },
126565
126566     right: function(e) {
126567         var menu = this.menu,
126568             fi = menu.focusedItem,
126569             ai = menu.activeItem,
126570             am;
126571
126572         if (fi && this.isWhitelisted(fi)) {
126573             return true;
126574         }
126575
126576         if (ai) {
126577             am = menu.activeItem.menu;
126578             if (am) {
126579                 ai.expandMenu(0);
126580                 Ext.defer(function() {
126581                     am.setActiveItem(am.items.getAt(0));
126582                 }, 25);
126583             }
126584         }
126585     },
126586
126587     tab: function(e) {
126588         var me = this;
126589
126590         if (e.shiftKey) {
126591             me.up(e);
126592         } else {
126593             me.down(e);
126594         }
126595     },
126596
126597     up: function(e) {
126598         var me = this,
126599             fi = me.menu.focusedItem;
126600
126601         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
126602             return true;
126603         }
126604         me.focusNextItem(-1);
126605     }
126606 });
126607 /**
126608  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
126609  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
126610  *
126611  *     @example
126612  *     Ext.create('Ext.menu.Menu', {
126613  *         width: 100,
126614  *         height: 100,
126615  *         floating: false,  // usually you want this set to True (default)
126616  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126617  *         items: [{
126618  *             text: 'icon item',
126619  *             iconCls: 'add16'
126620  *         },{
126621  *             xtype: 'menuseparator'
126622  *         },{
126623  *            text: 'seperator above',
126624  *         },{
126625  *            text: 'regular item',
126626  *         }]
126627  *     });
126628  */
126629 Ext.define('Ext.menu.Separator', {
126630     extend: 'Ext.menu.Item',
126631     alias: 'widget.menuseparator',
126632
126633     /**
126634      * @cfg {String} activeCls @hide
126635      */
126636
126637     /**
126638      * @cfg {Boolean} canActivate @hide
126639      */
126640     canActivate: false,
126641
126642     /**
126643      * @cfg {Boolean} clickHideDelay @hide
126644      */
126645
126646     /**
126647      * @cfg {Boolean} destroyMenu @hide
126648      */
126649
126650     /**
126651      * @cfg {Boolean} disabledCls @hide
126652      */
126653
126654     focusable: false,
126655
126656     /**
126657      * @cfg {String} href @hide
126658      */
126659
126660     /**
126661      * @cfg {String} hrefTarget @hide
126662      */
126663
126664     /**
126665      * @cfg {Boolean} hideOnClick @hide
126666      */
126667     hideOnClick: false,
126668
126669     /**
126670      * @cfg {String} icon @hide
126671      */
126672
126673     /**
126674      * @cfg {String} iconCls @hide
126675      */
126676
126677     /**
126678      * @cfg {Object} menu @hide
126679      */
126680
126681     /**
126682      * @cfg {String} menuAlign @hide
126683      */
126684
126685     /**
126686      * @cfg {Number} menuExpandDelay @hide
126687      */
126688
126689     /**
126690      * @cfg {Number} menuHideDelay @hide
126691      */
126692
126693     /**
126694      * @cfg {Boolean} plain @hide
126695      */
126696     plain: true,
126697
126698     /**
126699      * @cfg {String} separatorCls
126700      * The CSS class used by the separator item to show the incised line.
126701      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
126702      */
126703     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
126704
126705     /**
126706      * @cfg {String} text @hide
126707      */
126708     text: '&#160;',
126709
126710     onRender: function(ct, pos) {
126711         var me = this,
126712             sepCls = me.separatorCls;
126713
126714         me.cls += ' ' + sepCls;
126715
126716         me.callParent(arguments);
126717     }
126718 });
126719 /**
126720  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
126721  *
126722  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
126723  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
126724  *
126725  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
126726  * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
126727  * in line with the other menu items.
126728  *
126729  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
126730  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
126731  *
126732  *     @example
126733  *     Ext.create('Ext.menu.Menu', {
126734  *         width: 100,
126735  *         height: 100,
126736  *         margin: '0 0 10 0',
126737  *         floating: false,  // usually you want this set to True (default)
126738  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126739  *         items: [{
126740  *             text: 'regular item 1'
126741  *         },{
126742  *             text: 'regular item 2'
126743  *         },{
126744  *             text: 'regular item 3'
126745  *         }]
126746  *     });
126747  *
126748  *     Ext.create('Ext.menu.Menu', {
126749  *         width: 100,
126750  *         height: 100,
126751  *         plain: true,
126752  *         floating: false,  // usually you want this set to True (default)
126753  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126754  *         items: [{
126755  *             text: 'plain item 1'
126756  *         },{
126757  *             text: 'plain item 2'
126758  *         },{
126759  *             text: 'plain item 3'
126760  *         }]
126761  *     });
126762  */
126763 Ext.define('Ext.menu.Menu', {
126764     extend: 'Ext.panel.Panel',
126765     alias: 'widget.menu',
126766     requires: [
126767         'Ext.layout.container.Fit',
126768         'Ext.layout.container.VBox',
126769         'Ext.menu.CheckItem',
126770         'Ext.menu.Item',
126771         'Ext.menu.KeyNav',
126772         'Ext.menu.Manager',
126773         'Ext.menu.Separator'
126774     ],
126775
126776     /**
126777      * @property {Ext.menu.Menu} parentMenu
126778      * The parent Menu of this Menu.
126779      */
126780
126781     /**
126782      * @cfg {Boolean} allowOtherMenus
126783      * True to allow multiple menus to be displayed at the same time.
126784      */
126785     allowOtherMenus: false,
126786
126787     /**
126788      * @cfg {String} ariaRole @hide
126789      */
126790     ariaRole: 'menu',
126791
126792     /**
126793      * @cfg {Boolean} autoRender @hide
126794      * floating is true, so autoRender always happens
126795      */
126796
126797     /**
126798      * @cfg {String} defaultAlign
126799      * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
126800      * relative to its element of origin.
126801      */
126802     defaultAlign: 'tl-bl?',
126803
126804     /**
126805      * @cfg {Boolean} floating
126806      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
126807      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
126808      * used as a child item of another {@link Ext.container.Container Container}.
126809      */
126810     floating: true,
126811
126812     /**
126813      * @cfg {Boolean} @hide
126814      * Menus are constrained to the document body by default
126815      */
126816     constrain: true,
126817
126818     /**
126819      * @cfg {Boolean} [hidden=undefined]
126820      * True to initially render the Menu as hidden, requiring to be shown manually.
126821      *
126822      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
126823      */
126824     hidden: true,
126825
126826     hideMode: 'visibility',
126827
126828     /**
126829      * @cfg {Boolean} ignoreParentClicks
126830      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
126831      * so that the submenu is not dismissed when clicking the parent item.
126832      */
126833     ignoreParentClicks: false,
126834
126835     isMenu: true,
126836
126837     /**
126838      * @cfg {String/Object} layout @hide
126839      */
126840
126841     /**
126842      * @cfg {Boolean} showSeparator
126843      * True to show the icon separator.
126844      */
126845     showSeparator : true,
126846
126847     /**
126848      * @cfg {Number} minWidth
126849      * The minimum width of the Menu.
126850      */
126851     minWidth: 120,
126852
126853     /**
126854      * @cfg {Boolean} [plain=false]
126855      * True to remove the incised line down the left side of the menu and to not indent general Component items.
126856      */
126857
126858     initComponent: function() {
126859         var me = this,
126860             prefix = Ext.baseCSSPrefix,
126861             cls = [prefix + 'menu'],
126862             bodyCls = me.bodyCls ? [me.bodyCls] : [];
126863
126864         me.addEvents(
126865             /**
126866              * @event click
126867              * Fires when this menu is clicked
126868              * @param {Ext.menu.Menu} menu The menu which has been clicked
126869              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
126870              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
126871              */
126872             'click',
126873
126874             /**
126875              * @event mouseenter
126876              * Fires when the mouse enters this menu
126877              * @param {Ext.menu.Menu} menu The menu
126878              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126879              */
126880             'mouseenter',
126881
126882             /**
126883              * @event mouseleave
126884              * Fires when the mouse leaves this menu
126885              * @param {Ext.menu.Menu} menu The menu
126886              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126887              */
126888             'mouseleave',
126889
126890             /**
126891              * @event mouseover
126892              * Fires when the mouse is hovering over this menu
126893              * @param {Ext.menu.Menu} menu The menu
126894              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
126895              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126896              */
126897             'mouseover'
126898         );
126899
126900         Ext.menu.Manager.register(me);
126901
126902         // Menu classes
126903         if (me.plain) {
126904             cls.push(prefix + 'menu-plain');
126905         }
126906         me.cls = cls.join(' ');
126907
126908         // Menu body classes
126909         bodyCls.unshift(prefix + 'menu-body');
126910         me.bodyCls = bodyCls.join(' ');
126911
126912         // Internal vbox layout, with scrolling overflow
126913         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
126914         // options if we wish to allow for such configurations on the Menu.
126915         // e.g., scrolling speed, vbox align stretch, etc.
126916         me.layout = {
126917             type: 'vbox',
126918             align: 'stretchmax',
126919             autoSize: true,
126920             clearInnerCtOnLayout: true,
126921             overflowHandler: 'Scroller'
126922         };
126923
126924         // hidden defaults to false if floating is configured as false
126925         if (me.floating === false && me.initialConfig.hidden !== true) {
126926             me.hidden = false;
126927         }
126928
126929         me.callParent(arguments);
126930
126931         me.on('beforeshow', function() {
126932             var hasItems = !!me.items.length;
126933             // FIXME: When a menu has its show cancelled because of no items, it
126934             // gets a visibility: hidden applied to it (instead of the default display: none)
126935             // Not sure why, but we remove this style when we want to show again.
126936             if (hasItems && me.rendered) {
126937                 me.el.setStyle('visibility', null);
126938             }
126939             return hasItems;
126940         });
126941     },
126942
126943     afterRender: function(ct) {
126944         var me = this,
126945             prefix = Ext.baseCSSPrefix,
126946             space = '&#160;';
126947
126948         me.callParent(arguments);
126949
126950         // TODO: Move this to a subTemplate When we support them in the future
126951         if (me.showSeparator) {
126952             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
126953                 cls: prefix + 'menu-icon-separator',
126954                 html: space
126955             });
126956         }
126957
126958         me.focusEl = me.el.createChild({
126959             cls: prefix + 'menu-focus',
126960             tabIndex: '-1',
126961             html: space
126962         });
126963
126964         me.mon(me.el, {
126965             click: me.onClick,
126966             mouseover: me.onMouseOver,
126967             scope: me
126968         });
126969         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
126970
126971         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
126972             me.iconSepEl.setHeight(me.el.getHeight());
126973         }
126974
126975         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
126976     },
126977
126978     afterLayout: function() {
126979         var me = this;
126980         me.callParent(arguments);
126981
126982         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
126983         // floating elements inherit their parent's width, making them the width of
126984         // document.body instead of the width of their contents.
126985         // This includes left/right dock items.
126986         if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
126987             var innerCt = me.layout.getRenderTarget(),
126988                 innerCtWidth = 0,
126989                 dis = me.dockedItems,
126990                 l = dis.length,
126991                 i = 0,
126992                 di, clone, newWidth;
126993
126994             innerCtWidth = innerCt.getWidth();
126995
126996             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
126997
126998             // First set the body to the new width
126999             me.body.setWidth(newWidth);
127000
127001             // Now we calculate additional width (docked items) and set the el's width
127002             for (; i < l, di = dis.getAt(i); i++) {
127003                 if (di.dock == 'left' || di.dock == 'right') {
127004                     newWidth += di.getWidth();
127005                 }
127006             }
127007             me.el.setWidth(newWidth);
127008         }
127009     },
127010     
127011     getBubbleTarget: function(){
127012         return this.parentMenu || this.callParent();
127013     },
127014
127015     /**
127016      * Returns whether a menu item can be activated or not.
127017      * @return {Boolean}
127018      */
127019     canActivateItem: function(item) {
127020         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
127021     },
127022
127023     /**
127024      * Deactivates the current active item on the menu, if one exists.
127025      */
127026     deactivateActiveItem: function() {
127027         var me = this;
127028
127029         if (me.activeItem) {
127030             me.activeItem.deactivate();
127031             if (!me.activeItem.activated) {
127032                 delete me.activeItem;
127033             }
127034         }
127035
127036         // only blur if focusedItem is not a filter
127037         if (me.focusedItem && !me.filtered) {
127038             me.focusedItem.blur();
127039             if (!me.focusedItem.$focused) {
127040                 delete me.focusedItem;
127041             }
127042         }
127043     },
127044
127045     clearStretch: function () {
127046         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
127047         // reconsider them unless we clear the dimensions on the el's here:
127048         if (this.rendered) {
127049             this.items.each(function (item) {
127050                 // each menuItem component needs to layout again, so clear its cache
127051                 if (item.componentLayout) {
127052                     delete item.componentLayout.lastComponentSize;
127053                 }
127054                 if (item.el) {
127055                     item.el.setWidth(null);
127056                 }
127057             });
127058         }
127059     },
127060
127061     onAdd: function () {
127062         var me = this;
127063
127064         me.clearStretch();
127065         me.callParent(arguments);
127066
127067         if (Ext.isIE6 || Ext.isIE7) {
127068             // TODO - why does this need to be done (and not ok to do now)?
127069             Ext.Function.defer(me.doComponentLayout, 10, me);
127070         }
127071     },
127072
127073     onRemove: function () {
127074         this.clearStretch();
127075         this.callParent(arguments);
127076
127077     },
127078
127079     redoComponentLayout: function () {
127080         if (this.rendered) {
127081             this.clearStretch();
127082             this.doComponentLayout();
127083         }
127084     },
127085
127086     // inherit docs
127087     getFocusEl: function() {
127088         return this.focusEl;
127089     },
127090
127091     // inherit docs
127092     hide: function() {
127093         this.deactivateActiveItem();
127094         this.callParent(arguments);
127095     },
127096
127097     // private
127098     getItemFromEvent: function(e) {
127099         return this.getChildByElement(e.getTarget());
127100     },
127101
127102     lookupComponent: function(cmp) {
127103         var me = this;
127104
127105         if (Ext.isString(cmp)) {
127106             cmp = me.lookupItemFromString(cmp);
127107         } else if (Ext.isObject(cmp)) {
127108             cmp = me.lookupItemFromObject(cmp);
127109         }
127110
127111         // Apply our minWidth to all of our child components so it's accounted
127112         // for in our VBox layout
127113         cmp.minWidth = cmp.minWidth || me.minWidth;
127114
127115         return cmp;
127116     },
127117
127118     // private
127119     lookupItemFromObject: function(cmp) {
127120         var me = this,
127121             prefix = Ext.baseCSSPrefix,
127122             cls,
127123             intercept;
127124
127125         if (!cmp.isComponent) {
127126             if (!cmp.xtype) {
127127                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
127128             } else {
127129                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
127130             }
127131         }
127132
127133         if (cmp.isMenuItem) {
127134             cmp.parentMenu = me;
127135         }
127136
127137         if (!cmp.isMenuItem && !cmp.dock) {
127138             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
127139             intercept = Ext.Function.createInterceptor;
127140
127141             // Wrap focus/blur to control component focus
127142             cmp.focus = intercept(cmp.focus, function() {
127143                 this.$focused = true;
127144             }, cmp);
127145             cmp.blur = intercept(cmp.blur, function() {
127146                 this.$focused = false;
127147             }, cmp);
127148
127149             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
127150                 cls.push(prefix + 'menu-item-indent');
127151             }
127152
127153             if (cmp.rendered) {
127154                 cmp.el.addCls(cls);
127155             } else {
127156                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
127157             }
127158             cmp.isMenuItem = true;
127159         }
127160         return cmp;
127161     },
127162
127163     // private
127164     lookupItemFromString: function(cmp) {
127165         return (cmp == 'separator' || cmp == '-') ?
127166             Ext.createWidget('menuseparator')
127167             : Ext.createWidget('menuitem', {
127168                 canActivate: false,
127169                 hideOnClick: false,
127170                 plain: true,
127171                 text: cmp
127172             });
127173     },
127174
127175     onClick: function(e) {
127176         var me = this,
127177             item;
127178
127179         if (me.disabled) {
127180             e.stopEvent();
127181             return;
127182         }
127183
127184         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
127185             item = me.getItemFromEvent(e) || me.activeItem;
127186
127187             if (item) {
127188                 if (item.getXTypes().indexOf('menuitem') >= 0) {
127189                     if (!item.menu || !me.ignoreParentClicks) {
127190                         item.onClick(e);
127191                     } else {
127192                         e.stopEvent();
127193                     }
127194                 }
127195             }
127196             me.fireEvent('click', me, item, e);
127197         }
127198     },
127199
127200     onDestroy: function() {
127201         var me = this;
127202
127203         Ext.menu.Manager.unregister(me);
127204         if (me.rendered) {
127205             me.el.un(me.mouseMonitor);
127206             me.keyNav.destroy();
127207             delete me.keyNav;
127208         }
127209         me.callParent(arguments);
127210     },
127211
127212     onMouseLeave: function(e) {
127213         var me = this;
127214
127215         me.deactivateActiveItem();
127216
127217         if (me.disabled) {
127218             return;
127219         }
127220
127221         me.fireEvent('mouseleave', me, e);
127222     },
127223
127224     onMouseOver: function(e) {
127225         var me = this,
127226             fromEl = e.getRelatedTarget(),
127227             mouseEnter = !me.el.contains(fromEl),
127228             item = me.getItemFromEvent(e);
127229
127230         if (mouseEnter && me.parentMenu) {
127231             me.parentMenu.setActiveItem(me.parentItem);
127232             me.parentMenu.mouseMonitor.mouseenter();
127233         }
127234
127235         if (me.disabled) {
127236             return;
127237         }
127238
127239         if (item) {
127240             me.setActiveItem(item);
127241             if (item.activated && item.expandMenu) {
127242                 item.expandMenu();
127243             }
127244         }
127245         if (mouseEnter) {
127246             me.fireEvent('mouseenter', me, e);
127247         }
127248         me.fireEvent('mouseover', me, item, e);
127249     },
127250
127251     setActiveItem: function(item) {
127252         var me = this;
127253
127254         if (item && (item != me.activeItem && item != me.focusedItem)) {
127255             me.deactivateActiveItem();
127256             if (me.canActivateItem(item)) {
127257                 if (item.activate) {
127258                     item.activate();
127259                     if (item.activated) {
127260                         me.activeItem = item;
127261                         me.focusedItem = item;
127262                         me.focus();
127263                     }
127264                 } else {
127265                     item.focus();
127266                     me.focusedItem = item;
127267                 }
127268             }
127269             item.el.scrollIntoView(me.layout.getRenderTarget());
127270         }
127271     },
127272
127273     /**
127274      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
127275      * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
127276      * @param {String} position (optional) Alignment position as used by {@link Ext.Element#getAlignToXY}.
127277      * Defaults to `{@link #defaultAlign}`.
127278      * @param {Number[]} offsets (optional) Alignment offsets as used by {@link Ext.Element#getAlignToXY}. Defaults to `undefined`.
127279      * @return {Ext.menu.Menu} This Menu.
127280      */
127281     showBy: function(cmp, pos, off) {
127282         var me = this,
127283             xy,
127284             region;
127285
127286         if (me.floating && cmp) {
127287             me.layout.autoSize = true;
127288
127289             // show off-screen first so that we can calc position without causing a visual jump
127290             me.doAutoRender();
127291             delete me.needsLayout;
127292
127293             // Component or Element
127294             cmp = cmp.el || cmp;
127295
127296             // Convert absolute to floatParent-relative coordinates if necessary.
127297             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
127298             if (me.floatParent) {
127299                 region = me.floatParent.getTargetEl().getViewRegion();
127300                 xy[0] -= region.x;
127301                 xy[1] -= region.y;
127302             }
127303             me.showAt(xy);
127304         }
127305         return me;
127306     },
127307
127308     doConstrain : function() {
127309         var me = this,
127310             y = me.el.getY(),
127311             max, full,
127312             vector,
127313             returnY = y, normalY, parentEl, scrollTop, viewHeight;
127314
127315         delete me.height;
127316         me.setSize();
127317         full = me.getHeight();
127318         if (me.floating) {
127319             //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
127320             parentEl = Ext.fly(me.el.getScopeParent());
127321             scrollTop = parentEl.getScroll().top;
127322             viewHeight = parentEl.getViewSize().height;
127323             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
127324             //of the view.
127325             normalY = y - scrollTop;
127326             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
127327             if (full > viewHeight) {
127328                 max = viewHeight;
127329                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
127330                 returnY = y - normalY;
127331             } else if (max < full) {
127332                 returnY = y - (full - max);
127333                 max = full;
127334             }
127335         }else{
127336             max = me.getHeight();
127337         }
127338         // Always respect maxHeight
127339         if (me.maxHeight){
127340             max = Math.min(me.maxHeight, max);
127341         }
127342         if (full > max && max > 0){
127343             me.layout.autoSize = false;
127344             me.setHeight(max);
127345             if (me.showSeparator){
127346                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
127347             }
127348         }
127349         vector = me.getConstrainVector(me.el.getScopeParent());
127350         if (vector) {
127351             me.setPosition(me.getPosition()[0] + vector[0]);
127352         }
127353         me.el.setY(returnY);
127354     }
127355 });
127356
127357 /**
127358  * A menu containing a Ext.picker.Color Component.
127359  *
127360  * Notes:
127361  *
127362  *   - Although not listed here, the **constructor** for this class accepts all of the
127363  *     configuration options of {@link Ext.picker.Color}.
127364  *   - If subclassing ColorMenu, any configuration options for the ColorPicker must be
127365  *     applied to the **initialConfig** property of the ColorMenu. Applying
127366  *     {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
127367  *     affect the ColorPicker's configuration.
127368  *
127369  * Example:
127370  *
127371  *     @example
127372  *     var colorPicker = Ext.create('Ext.menu.ColorPicker', {
127373  *         value: '000000'
127374  *     });
127375  *
127376  *     Ext.create('Ext.menu.Menu', {
127377  *         width: 100,
127378  *         height: 90,
127379  *         floating: false,  // usually you want this set to True (default)
127380  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
127381  *         items: [{
127382  *             text: 'choose a color',
127383  *             menu: colorPicker
127384  *         },{
127385  *             iconCls: 'add16',
127386  *             text: 'icon item'
127387  *         },{
127388  *             text: 'regular item'
127389  *         }]
127390  *     });
127391  */
127392  Ext.define('Ext.menu.ColorPicker', {
127393      extend: 'Ext.menu.Menu',
127394
127395      alias: 'widget.colormenu',
127396
127397      requires: [
127398         'Ext.picker.Color'
127399      ],
127400
127401     /**
127402      * @cfg {Boolean} hideOnClick
127403      * False to continue showing the menu after a date is selected.
127404      */
127405     hideOnClick : true,
127406
127407     /**
127408      * @cfg {String} pickerId
127409      * An id to assign to the underlying color picker.
127410      */
127411     pickerId : null,
127412
127413     /**
127414      * @cfg {Number} maxHeight
127415      * @hide
127416      */
127417
127418     /**
127419      * @property {Ext.picker.Color} picker
127420      * The {@link Ext.picker.Color} instance for this ColorMenu
127421      */
127422
127423     /**
127424      * @event click
127425      * @hide
127426      */
127427
127428     /**
127429      * @event itemclick
127430      * @hide
127431      */
127432
127433     initComponent : function(){
127434         var me = this,
127435             cfg = Ext.apply({}, me.initialConfig);
127436
127437         // Ensure we don't get duplicate listeners
127438         delete cfg.listeners;
127439         Ext.apply(me, {
127440             plain: true,
127441             showSeparator: false,
127442             items: Ext.applyIf({
127443                 cls: Ext.baseCSSPrefix + 'menu-color-item',
127444                 id: me.pickerId,
127445                 xtype: 'colorpicker'
127446             }, cfg)
127447         });
127448
127449         me.callParent(arguments);
127450
127451         me.picker = me.down('colorpicker');
127452
127453         /**
127454          * @event select
127455          * @alias Ext.picker.Color#select
127456          */
127457         me.relayEvents(me.picker, ['select']);
127458
127459         if (me.hideOnClick) {
127460             me.on('select', me.hidePickerOnSelect, me);
127461         }
127462     },
127463
127464     /**
127465      * Hides picker on select if hideOnClick is true
127466      * @private
127467      */
127468     hidePickerOnSelect: function() {
127469         Ext.menu.Manager.hideAll();
127470     }
127471  });
127472 /**
127473  * A menu containing an Ext.picker.Date Component.
127474  *
127475  * Notes:
127476  *
127477  * - Although not listed here, the **constructor** for this class accepts all of the
127478  *   configuration options of **{@link Ext.picker.Date}**.
127479  * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
127480  *   to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
127481  *   configuration settings to **this** will **not** affect the Date Picker's configuration.
127482  *
127483  * Example:
127484  *
127485  *     @example
127486  *     var dateMenu = Ext.create('Ext.menu.DatePicker', {
127487  *         handler: function(dp, date){
127488  *             Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
127489  *         }
127490  *     });
127491  *
127492  *     Ext.create('Ext.menu.Menu', {
127493  *         width: 100,
127494  *         height: 90,
127495  *         floating: false,  // usually you want this set to True (default)
127496  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
127497  *         items: [{
127498  *             text: 'choose a date',
127499  *             menu: dateMenu
127500  *         },{
127501  *             iconCls: 'add16',
127502  *             text: 'icon item'
127503  *         },{
127504  *             text: 'regular item'
127505  *         }]
127506  *     });
127507  */
127508  Ext.define('Ext.menu.DatePicker', {
127509      extend: 'Ext.menu.Menu',
127510
127511      alias: 'widget.datemenu',
127512
127513      requires: [
127514         'Ext.picker.Date'
127515      ],
127516
127517     /**
127518      * @cfg {Boolean} hideOnClick
127519      * False to continue showing the menu after a date is selected.
127520      */
127521     hideOnClick : true,
127522
127523     /**
127524      * @cfg {String} pickerId
127525      * An id to assign to the underlying date picker.
127526      */
127527     pickerId : null,
127528
127529     /**
127530      * @cfg {Number} maxHeight
127531      * @hide
127532      */
127533
127534     /**
127535      * @property {Ext.picker.Date} picker
127536      * The {@link Ext.picker.Date} instance for this DateMenu
127537      */
127538
127539     /**
127540      * @event click
127541      * @hide
127542      */
127543
127544     /**
127545      * @event itemclick
127546      * @hide
127547      */
127548
127549     initComponent : function(){
127550         var me = this;
127551
127552         Ext.apply(me, {
127553             showSeparator: false,
127554             plain: true,
127555             border: false,
127556             bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
127557             items: Ext.applyIf({
127558                 cls: Ext.baseCSSPrefix + 'menu-date-item',
127559                 id: me.pickerId,
127560                 xtype: 'datepicker'
127561             }, me.initialConfig)
127562         });
127563
127564         me.callParent(arguments);
127565
127566         me.picker = me.down('datepicker');
127567         /**
127568          * @event select
127569          * @alias Ext.picker.Date#select
127570          */
127571         me.relayEvents(me.picker, ['select']);
127572
127573         if (me.hideOnClick) {
127574             me.on('select', me.hidePickerOnSelect, me);
127575         }
127576     },
127577
127578     hidePickerOnSelect: function() {
127579         Ext.menu.Manager.hideAll();
127580     }
127581  });
127582 /**
127583  * This class is used to display small visual icons in the header of a panel. There are a set of
127584  * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
127585  * can be used to provide a function that will respond to any click events. In general, this class
127586  * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
127587  * configuration on the Panel itself.
127588  *
127589  *     @example
127590  *     Ext.create('Ext.panel.Panel', {
127591  *         width: 200,
127592  *         height: 200,
127593  *         renderTo: document.body,
127594  *         title: 'A Panel',
127595  *         tools: [{
127596  *             type: 'help',
127597  *             handler: function(){
127598  *                 // show help here
127599  *             }
127600  *         }, {
127601  *             itemId: 'refresh',
127602  *             type: 'refresh',
127603  *             hidden: true,
127604  *             handler: function(){
127605  *                 // do refresh
127606  *             }
127607  *         }, {
127608  *             type: 'search',
127609  *             handler: function(event, target, owner, tool){
127610  *                 // do search
127611  *                 owner.child('#refresh').show();
127612  *             }
127613  *         }]
127614  *     });
127615  */
127616 Ext.define('Ext.panel.Tool', {
127617     extend: 'Ext.Component',
127618     requires: ['Ext.tip.QuickTipManager'],
127619     alias: 'widget.tool',
127620
127621     baseCls: Ext.baseCSSPrefix + 'tool',
127622     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
127623     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
127624     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
127625     ariaRole: 'button',
127626     renderTpl: ['<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
127627
127628     /**
127629      * @cfg {Function} handler
127630      * A function to execute when the tool is clicked. Arguments passed are:
127631      *
127632      * - **event** : Ext.EventObject - The click event.
127633      * - **toolEl** : Ext.Element - The tool Element.
127634      * - **owner** : Ext.panel.Header - The host panel header.
127635      * - **tool** : Ext.panel.Tool - The tool object
127636      */
127637
127638     /**
127639      * @cfg {Object} scope
127640      * The scope to execute the {@link #handler} function. Defaults to the tool.
127641      */
127642
127643     /**
127644      * @cfg {String} type
127645      * The type of tool to render. The following types are available:
127646      *
127647      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
127648      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
127649      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
127650      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
127651      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
127652      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
127653      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
127654      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
127655      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
127656      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
127657      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
127658      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
127659      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
127660      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
127661      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
127662      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
127663      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
127664      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
127665      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
127666      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
127667      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
127668      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
127669      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
127670      */
127671
127672     /**
127673      * @cfg {String/Object} tooltip
127674      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
127675      * object
127676      */
127677
127678      /**
127679      * @cfg {String} tooltipType
127680      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
127681      */
127682     tooltipType: 'qtip',
127683
127684     /**
127685      * @cfg {Boolean} stopEvent
127686      * Specify as false to allow click event to propagate.
127687      */
127688     stopEvent: true,
127689
127690     initComponent: function() {
127691         var me = this;
127692         me.addEvents(
127693             /**
127694              * @event click
127695              * Fires when the tool is clicked
127696              * @param {Ext.panel.Tool} this
127697              * @param {Ext.EventObject} e The event object
127698              */
127699             'click'
127700         );
127701
127702         var types = [
127703             'close',
127704             'collapse',
127705             'down',
127706             'expand',
127707             'gear',
127708             'help',
127709             'left',
127710             'maximize',
127711             'minimize',
127712             'minus',
127713             'move',
127714             'next',
127715             'pin',
127716             'plus',
127717             'prev',
127718             'print',
127719             'refresh',
127720             'resize',
127721             'restore',
127722             'right',
127723             'save',
127724             'search',
127725             'toggle',
127726             'unpin',
127727             'up'
127728         ];
127729
127730         if (me.id && Ext.Array.indexOf(types, me.id) > -1 && Ext.global.console) {
127731             Ext.global.console.warn('When specifying a tool you should use the type option, the id can conflict now that tool is a Component');
127732         }
127733
127734         me.type = me.type || me.id;
127735
127736         Ext.applyIf(me.renderData, {
127737             baseCls: me.baseCls,
127738             blank: Ext.BLANK_IMAGE_URL,
127739             type: me.type
127740         });
127741
127742         me.addChildEls('toolEl');
127743
127744         // alias qtip, should use tooltip since it's what we have in the docs
127745         me.tooltip = me.tooltip || me.qtip;
127746         me.callParent();
127747     },
127748
127749     // inherit docs
127750     afterRender: function() {
127751         var me = this,
127752             attr;
127753
127754         me.callParent(arguments);
127755         if (me.tooltip) {
127756             if (Ext.isObject(me.tooltip)) {
127757                 Ext.tip.QuickTipManager.register(Ext.apply({
127758                     target: me.id
127759                 }, me.tooltip));
127760             }
127761             else {
127762                 attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
127763                 me.toolEl.dom.setAttribute(attr, me.tooltip);
127764             }
127765         }
127766
127767         me.mon(me.toolEl, {
127768             click: me.onClick,
127769             mousedown: me.onMouseDown,
127770             mouseover: me.onMouseOver,
127771             mouseout: me.onMouseOut,
127772             scope: me
127773         });
127774     },
127775
127776     /**
127777      * Sets the type of the tool. Allows the icon to be changed.
127778      * @param {String} type The new type. See the {@link #type} config.
127779      * @return {Ext.panel.Tool} this
127780      */
127781     setType: function(type) {
127782         var me = this;
127783
127784         me.type = type;
127785         if (me.rendered) {
127786             me.toolEl.dom.className = me.baseCls + '-' + type;
127787         }
127788         return me;
127789     },
127790
127791     /**
127792      * Binds this tool to a component.
127793      * @private
127794      * @param {Ext.Component} component The component
127795      */
127796     bindTo: function(component) {
127797         this.owner = component;
127798     },
127799
127800     /**
127801      * Called when the tool element is clicked
127802      * @private
127803      * @param {Ext.EventObject} e
127804      * @param {HTMLElement} target The target element
127805      */
127806     onClick: function(e, target) {
127807         var me = this,
127808             owner;
127809
127810         if (me.disabled) {
127811             return false;
127812         }
127813         owner = me.owner || me.ownerCt;
127814
127815         //remove the pressed + over class
127816         me.el.removeCls(me.toolPressedCls);
127817         me.el.removeCls(me.toolOverCls);
127818
127819         if (me.stopEvent !== false) {
127820             e.stopEvent();
127821         }
127822
127823         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
127824         me.fireEvent('click', me, e);
127825         return true;
127826     },
127827
127828     // inherit docs
127829     onDestroy: function(){
127830         if (Ext.isObject(this.tooltip)) {
127831             Ext.tip.QuickTipManager.unregister(this.id);
127832         }
127833         this.callParent();
127834     },
127835
127836     /**
127837      * Called when the user presses their mouse button down on a tool
127838      * Adds the press class ({@link #toolPressedCls})
127839      * @private
127840      */
127841     onMouseDown: function() {
127842         if (this.disabled) {
127843             return false;
127844         }
127845
127846         this.el.addCls(this.toolPressedCls);
127847     },
127848
127849     /**
127850      * Called when the user rolls over a tool
127851      * Adds the over class ({@link #toolOverCls})
127852      * @private
127853      */
127854     onMouseOver: function() {
127855         if (this.disabled) {
127856             return false;
127857         }
127858         this.el.addCls(this.toolOverCls);
127859     },
127860
127861     /**
127862      * Called when the user rolls out from a tool.
127863      * Removes the over class ({@link #toolOverCls})
127864      * @private
127865      */
127866     onMouseOut: function() {
127867         this.el.removeCls(this.toolOverCls);
127868     }
127869 });
127870 /**
127871  * @class Ext.resizer.Handle
127872  * @extends Ext.Component
127873  *
127874  * Provides a handle for 9-point resizing of Elements or Components.
127875  */
127876 Ext.define('Ext.resizer.Handle', {
127877     extend: 'Ext.Component',
127878     handleCls: '',
127879     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
127880     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
127881     // which will be passed in as a region configuration.
127882     region: '',
127883
127884     onRender: function() {
127885         this.addCls(
127886             this.baseHandleCls,
127887             this.baseHandleCls + '-' + this.region,
127888             this.handleCls
127889         );
127890         this.callParent(arguments);
127891         this.el.unselectable();
127892     }
127893 });
127894
127895 /**
127896  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
127897  * (or component's element) and positioned absolute.
127898  *
127899  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
127900  * The original element can be accessed through the originalTarget property.
127901  *
127902  * Here is the list of valid resize handles:
127903  *
127904  *     Value   Description
127905  *     ------  -------------------
127906  *      'n'     north
127907  *      's'     south
127908  *      'e'     east
127909  *      'w'     west
127910  *      'nw'    northwest
127911  *      'sw'    southwest
127912  *      'se'    southeast
127913  *      'ne'    northeast
127914  *      'all'   all
127915  *
127916  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
127917  *
127918  * Here's an example showing the creation of a typical Resizer:
127919  *
127920  *     Ext.create('Ext.resizer.Resizer', {
127921  *         el: 'elToResize',
127922  *         handles: 'all',
127923  *         minWidth: 200,
127924  *         minHeight: 100,
127925  *         maxWidth: 500,
127926  *         maxHeight: 400,
127927  *         pinned: true
127928  *     });
127929  */
127930 Ext.define('Ext.resizer.Resizer', {
127931     mixins: {
127932         observable: 'Ext.util.Observable'
127933     },
127934     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
127935
127936     alternateClassName: 'Ext.Resizable',
127937
127938     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
127939     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
127940     overCls:   Ext.baseCSSPrefix + 'resizable-over',
127941     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
127942
127943     /**
127944      * @cfg {Boolean} dynamic
127945      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
127946      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
127947      * configured as {@link Ext.Component#resizable}.
127948      *
127949      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
127950      * updated on mouseup.
127951      */
127952     dynamic: true,
127953
127954     /**
127955      * @cfg {String} handles
127956      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
127957      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
127958      * of `'n s e w ne nw se sw'`.
127959      */
127960     handles: 's e se',
127961
127962     /**
127963      * @cfg {Number} height
127964      * Optional. The height to set target to in pixels
127965      */
127966     height : null,
127967
127968     /**
127969      * @cfg {Number} width
127970      * Optional. The width to set the target to in pixels
127971      */
127972     width : null,
127973
127974     /**
127975      * @cfg {Number} heightIncrement
127976      * The increment to snap the height resize in pixels.
127977      */
127978     heightIncrement : 0,
127979
127980     /**
127981      * @cfg {Number} widthIncrement
127982      * The increment to snap the width resize in pixels.
127983      */
127984     widthIncrement : 0,
127985
127986     /**
127987      * @cfg {Number} minHeight
127988      * The minimum height for the element
127989      */
127990     minHeight : 20,
127991
127992     /**
127993      * @cfg {Number} minWidth
127994      * The minimum width for the element
127995      */
127996     minWidth : 20,
127997
127998     /**
127999      * @cfg {Number} maxHeight
128000      * The maximum height for the element
128001      */
128002     maxHeight : 10000,
128003
128004     /**
128005      * @cfg {Number} maxWidth
128006      * The maximum width for the element
128007      */
128008     maxWidth : 10000,
128009
128010     /**
128011      * @cfg {Boolean} pinned
128012      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
128013      */
128014     pinned: false,
128015
128016     /**
128017      * @cfg {Boolean} preserveRatio
128018      * True to preserve the original ratio between height and width during resize
128019      */
128020     preserveRatio: false,
128021
128022     /**
128023      * @cfg {Boolean} transparent
128024      * True for transparent handles. This is only applied at config time.
128025      */
128026     transparent: false,
128027
128028     /**
128029      * @cfg {Ext.Element/Ext.util.Region} constrainTo
128030      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
128031      */
128032
128033     possiblePositions: {
128034         n:  'north',
128035         s:  'south',
128036         e:  'east',
128037         w:  'west',
128038         se: 'southeast',
128039         sw: 'southwest',
128040         nw: 'northwest',
128041         ne: 'northeast'
128042     },
128043
128044     /**
128045      * @cfg {Ext.Element/Ext.Component} target
128046      * The Element or Component to resize.
128047      */
128048
128049     /**
128050      * @property {Ext.Element} el
128051      * Outer element for resizing behavior.
128052      */
128053
128054     constructor: function(config) {
128055         var me = this,
128056             target,
128057             tag,
128058             handles = me.handles,
128059             handleCls,
128060             possibles,
128061             len,
128062             i = 0,
128063             pos;
128064
128065         this.addEvents(
128066             /**
128067              * @event beforeresize
128068              * Fired before resize is allowed. Return false to cancel resize.
128069              * @param {Ext.resizer.Resizer} this
128070              * @param {Number} width The start width
128071              * @param {Number} height The start height
128072              * @param {Ext.EventObject} e The mousedown event
128073              */
128074             'beforeresize',
128075             /**
128076              * @event resizedrag
128077              * Fires during resizing. Return false to cancel resize.
128078              * @param {Ext.resizer.Resizer} this
128079              * @param {Number} width The new width
128080              * @param {Number} height The new height
128081              * @param {Ext.EventObject} e The mousedown event
128082              */
128083             'resizedrag',
128084             /**
128085              * @event resize
128086              * Fired after a resize.
128087              * @param {Ext.resizer.Resizer} this
128088              * @param {Number} width The new width
128089              * @param {Number} height The new height
128090              * @param {Ext.EventObject} e The mouseup event
128091              */
128092             'resize'
128093         );
128094
128095         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
128096             target = config;
128097             config = arguments[1] || {};
128098             config.target = target;
128099         }
128100         // will apply config to this
128101         me.mixins.observable.constructor.call(me, config);
128102
128103         // If target is a Component, ensure that we pull the element out.
128104         // Resizer must examine the underlying Element.
128105         target = me.target;
128106         if (target) {
128107             if (target.isComponent) {
128108                 me.el = target.getEl();
128109                 if (target.minWidth) {
128110                     me.minWidth = target.minWidth;
128111                 }
128112                 if (target.minHeight) {
128113                     me.minHeight = target.minHeight;
128114                 }
128115                 if (target.maxWidth) {
128116                     me.maxWidth = target.maxWidth;
128117                 }
128118                 if (target.maxHeight) {
128119                     me.maxHeight = target.maxHeight;
128120                 }
128121                 if (target.floating) {
128122                     if (!this.hasOwnProperty('handles')) {
128123                         this.handles = 'n ne e se s sw w nw';
128124                     }
128125                 }
128126             } else {
128127                 me.el = me.target = Ext.get(target);
128128             }
128129         }
128130         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
128131         else {
128132             me.target = me.el = Ext.get(me.el);
128133         }
128134
128135         // Tags like textarea and img cannot
128136         // have children and therefore must
128137         // be wrapped
128138         tag = me.el.dom.tagName;
128139         if (tag == 'TEXTAREA' || tag == 'IMG') {
128140             /**
128141              * @property {Ext.Element/Ext.Component} originalTarget
128142              * Reference to the original resize target if the element of the original resize target was an IMG or a
128143              * TEXTAREA which must be wrapped in a DIV.
128144              */
128145             me.originalTarget = me.target;
128146             me.target = me.el = me.el.wrap({
128147                 cls: me.wrapCls,
128148                 id: me.el.id + '-rzwrap'
128149             });
128150
128151             // Transfer originalTarget's positioning/sizing
128152             me.el.setPositioning(me.originalTarget.getPositioning());
128153             me.originalTarget.clearPositioning();
128154             var box = me.originalTarget.getBox();
128155             me.el.setBox(box);
128156         }
128157
128158         // Position the element, this enables us to absolute position
128159         // the handles within this.el
128160         me.el.position();
128161         if (me.pinned) {
128162             me.el.addCls(me.pinnedCls);
128163         }
128164
128165         /**
128166          * @property {Ext.resizer.ResizeTracker} resizeTracker
128167          */
128168         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
128169             disabled: me.disabled,
128170             target: me.target,
128171             constrainTo: me.constrainTo,
128172             overCls: me.overCls,
128173             throttle: me.throttle,
128174             originalTarget: me.originalTarget,
128175             delegate: '.' + me.handleCls,
128176             dynamic: me.dynamic,
128177             preserveRatio: me.preserveRatio,
128178             heightIncrement: me.heightIncrement,
128179             widthIncrement: me.widthIncrement,
128180             minHeight: me.minHeight,
128181             maxHeight: me.maxHeight,
128182             minWidth: me.minWidth,
128183             maxWidth: me.maxWidth
128184         });
128185
128186         // Relay the ResizeTracker's superclass events as our own resize events
128187         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
128188         me.resizeTracker.on('drag', me.onResize, me);
128189         me.resizeTracker.on('dragend', me.onResizeEnd, me);
128190
128191         if (me.handles == 'all') {
128192             me.handles = 'n s e w ne nw se sw';
128193         }
128194
128195         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
128196         possibles = me.possiblePositions;
128197         len = handles.length;
128198         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
128199
128200         for(; i < len; i++){
128201             // if specified and possible, create
128202             if (handles[i] && possibles[handles[i]]) {
128203                 pos = possibles[handles[i]];
128204                 // store a reference in this.east, this.west, etc
128205
128206                 me[pos] = Ext.create('Ext.Component', {
128207                     owner: this,
128208                     region: pos,
128209                     cls: handleCls + pos,
128210                     renderTo: me.el
128211                 });
128212                 me[pos].el.unselectable();
128213                 if (me.transparent) {
128214                     me[pos].el.setOpacity(0);
128215                 }
128216             }
128217         }
128218
128219         // Constrain within configured maxima
128220         if (Ext.isNumber(me.width)) {
128221             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
128222         }
128223         if (Ext.isNumber(me.height)) {
128224             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
128225         }
128226
128227         // Size the element
128228         if (me.width != null || me.height != null) {
128229             if (me.originalTarget) {
128230                 me.originalTarget.setWidth(me.width);
128231                 me.originalTarget.setHeight(me.height);
128232             }
128233             me.resizeTo(me.width, me.height);
128234         }
128235
128236         me.forceHandlesHeight();
128237     },
128238
128239     disable: function() {
128240         this.resizeTracker.disable();
128241     },
128242
128243     enable: function() {
128244         this.resizeTracker.enable();
128245     },
128246
128247     /**
128248      * @private Relay the Tracker's mousedown event as beforeresize
128249      * @param tracker The Resizer
128250      * @param e The Event
128251      */
128252     onBeforeResize: function(tracker, e) {
128253         var b = this.target.getBox();
128254         return this.fireEvent('beforeresize', this, b.width, b.height, e);
128255     },
128256
128257     /**
128258      * @private Relay the Tracker's drag event as resizedrag
128259      * @param tracker The Resizer
128260      * @param e The Event
128261      */
128262     onResize: function(tracker, e) {
128263         var me = this,
128264             b = me.target.getBox();
128265         me.forceHandlesHeight();
128266         return me.fireEvent('resizedrag', me, b.width, b.height, e);
128267     },
128268
128269     /**
128270      * @private Relay the Tracker's dragend event as resize
128271      * @param tracker The Resizer
128272      * @param e The Event
128273      */
128274     onResizeEnd: function(tracker, e) {
128275         var me = this,
128276             b = me.target.getBox();
128277         me.forceHandlesHeight();
128278         return me.fireEvent('resize', me, b.width, b.height, e);
128279     },
128280
128281     /**
128282      * Perform a manual resize and fires the 'resize' event.
128283      * @param {Number} width
128284      * @param {Number} height
128285      */
128286     resizeTo : function(width, height){
128287         this.target.setSize(width, height);
128288         this.fireEvent('resize', this, width, height, null);
128289     },
128290
128291     /**
128292      * Returns the element that was configured with the el or target config property. If a component was configured with
128293      * the target property then this will return the element of this component.
128294      *
128295      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
128296      * nodes. The original element can be accessed through the originalTarget property.
128297      * @return {Ext.Element} element
128298      */
128299     getEl : function() {
128300         return this.el;
128301     },
128302
128303     /**
128304      * Returns the element or component that was configured with the target config property.
128305      *
128306      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
128307      * nodes. The original element can be accessed through the originalTarget property.
128308      * @return {Ext.Element/Ext.Component}
128309      */
128310     getTarget: function() {
128311         return this.target;
128312     },
128313
128314     destroy: function() {
128315         var h;
128316         for (var i = 0, l = this.handles.length; i < l; i++) {
128317             h = this[this.possiblePositions[this.handles[i]]];
128318             delete h.owner;
128319             Ext.destroy(h);
128320         }
128321     },
128322
128323     /**
128324      * @private
128325      * Fix IE6 handle height issue.
128326      */
128327     forceHandlesHeight : function() {
128328         var me = this,
128329             handle;
128330         if (Ext.isIE6) {
128331             handle = me.east;
128332             if (handle) {
128333                 handle.setHeight(me.el.getHeight());
128334             }
128335             handle = me.west;
128336             if (handle) {
128337                 handle.setHeight(me.el.getHeight());
128338             }
128339             me.el.repaint();
128340         }
128341     }
128342 });
128343
128344 /**
128345  * @class Ext.resizer.ResizeTracker
128346  * @extends Ext.dd.DragTracker
128347  * Private utility class for Ext.resizer.Resizer.
128348  * @private
128349  */
128350 Ext.define('Ext.resizer.ResizeTracker', {
128351     extend: 'Ext.dd.DragTracker',
128352     dynamic: true,
128353     preserveRatio: false,
128354
128355     // Default to no constraint
128356     constrainTo: null,
128357     
128358     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
128359
128360     constructor: function(config) {
128361         var me = this;
128362
128363         if (!config.el) {
128364             if (config.target.isComponent) {
128365                 me.el = config.target.getEl();
128366             } else {
128367                 me.el = config.target;
128368             }
128369         }
128370         this.callParent(arguments);
128371
128372         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
128373         if (me.preserveRatio && me.minWidth && me.minHeight) {
128374             var widthRatio = me.minWidth / me.el.getWidth(),
128375                 heightRatio = me.minHeight / me.el.getHeight();
128376
128377             // largest ratio of minimum:size must be preserved.
128378             // So if a 400x200 pixel image has
128379             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
128380             if (heightRatio > widthRatio) {
128381                 me.minWidth = me.el.getWidth() * heightRatio;
128382             } else {
128383                 me.minHeight = me.el.getHeight() * widthRatio;
128384             }
128385         }
128386
128387         // If configured as throttled, create an instance version of resize which calls
128388         // a throttled function to perform the resize operation.
128389         if (me.throttle) {
128390             var throttledResizeFn = Ext.Function.createThrottled(function() {
128391                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
128392                 }, me.throttle);
128393
128394             me.resize = function(box, direction, atEnd) {
128395                 if (atEnd) {
128396                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
128397                 } else {
128398                     throttledResizeFn.apply(null, arguments);
128399                 }
128400             };
128401         }
128402     },
128403
128404     onBeforeStart: function(e) {
128405         // record the startBox
128406         this.startBox = this.el.getBox();
128407     },
128408
128409     /**
128410      * @private
128411      * Returns the object that will be resized on every mousemove event.
128412      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
128413      */
128414     getDynamicTarget: function() {
128415         var me = this,
128416             target = me.target;
128417             
128418         if (me.dynamic) {
128419             return target;
128420         } else if (!me.proxy) {
128421             me.proxy = me.createProxy(target);
128422         }
128423         me.proxy.show();
128424         return me.proxy;
128425     },
128426     
128427     /**
128428      * Create a proxy for this resizer
128429      * @param {Ext.Component/Ext.Element} target The target
128430      * @return {Ext.Element} A proxy element
128431      */
128432     createProxy: function(target){
128433         var proxy,
128434             cls = this.proxyCls,
128435             renderTo;
128436             
128437         if (target.isComponent) {
128438             proxy = target.getProxy().addCls(cls);
128439         } else {
128440             renderTo = Ext.getBody();
128441             if (Ext.scopeResetCSS) {
128442                 renderTo = Ext.getBody().createChild({
128443                     cls: Ext.baseCSSPrefix + 'reset'
128444                 });
128445             }
128446             proxy = target.createProxy({
128447                 tag: 'div',
128448                 cls: cls,
128449                 id: target.id + '-rzproxy'
128450             }, renderTo);
128451         }
128452         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
128453         return proxy;
128454     },
128455
128456     onStart: function(e) {
128457         // returns the Ext.ResizeHandle that the user started dragging
128458         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
128459
128460         // If we are using a proxy, ensure it is sized.
128461         if (!this.dynamic) {
128462             this.resize(this.startBox, {
128463                 horizontal: 'none',
128464                 vertical: 'none'
128465             });
128466         }
128467     },
128468
128469     onDrag: function(e) {
128470         // dynamic resizing, update dimensions during resize
128471         if (this.dynamic || this.proxy) {
128472             this.updateDimensions(e);
128473         }
128474     },
128475
128476     updateDimensions: function(e, atEnd) {
128477         var me = this,
128478             region = me.activeResizeHandle.region,
128479             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
128480             box = me.startBox,
128481             ratio,
128482             widthAdjust = 0,
128483             heightAdjust = 0,
128484             snappedWidth,
128485             snappedHeight,
128486             adjustX = 0,
128487             adjustY = 0,
128488             dragRatio,
128489             horizDir = offset[0] < 0 ? 'right' : 'left',
128490             vertDir = offset[1] < 0 ? 'down' : 'up',
128491             oppositeCorner,
128492             axis; // 1 = x, 2 = y, 3 = x and y.
128493
128494         switch (region) {
128495             case 'south':
128496                 heightAdjust = offset[1];
128497                 axis = 2;
128498                 break;
128499             case 'north':
128500                 heightAdjust = -offset[1];
128501                 adjustY = -heightAdjust;
128502                 axis = 2;
128503                 break;
128504             case 'east':
128505                 widthAdjust = offset[0];
128506                 axis = 1;
128507                 break;
128508             case 'west':
128509                 widthAdjust = -offset[0];
128510                 adjustX = -widthAdjust;
128511                 axis = 1;
128512                 break;
128513             case 'northeast':
128514                 heightAdjust = -offset[1];
128515                 adjustY = -heightAdjust;
128516                 widthAdjust = offset[0];
128517                 oppositeCorner = [box.x, box.y + box.height];
128518                 axis = 3;
128519                 break;
128520             case 'southeast':
128521                 heightAdjust = offset[1];
128522                 widthAdjust = offset[0];
128523                 oppositeCorner = [box.x, box.y];
128524                 axis = 3;
128525                 break;
128526             case 'southwest':
128527                 widthAdjust = -offset[0];
128528                 adjustX = -widthAdjust;
128529                 heightAdjust = offset[1];
128530                 oppositeCorner = [box.x + box.width, box.y];
128531                 axis = 3;
128532                 break;
128533             case 'northwest':
128534                 heightAdjust = -offset[1];
128535                 adjustY = -heightAdjust;
128536                 widthAdjust = -offset[0];
128537                 adjustX = -widthAdjust;
128538                 oppositeCorner = [box.x + box.width, box.y + box.height];
128539                 axis = 3;
128540                 break;
128541         }
128542
128543         var newBox = {
128544             width: box.width + widthAdjust,
128545             height: box.height + heightAdjust,
128546             x: box.x + adjustX,
128547             y: box.y + adjustY
128548         };
128549
128550         // Snap value between stops according to configured increments
128551         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
128552         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
128553         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
128554             switch (region) {
128555                 case 'northeast':
128556                     newBox.y -= snappedHeight - newBox.height;
128557                     break;
128558                 case 'north':
128559                     newBox.y -= snappedHeight - newBox.height;
128560                     break;
128561                 case 'southwest':
128562                     newBox.x -= snappedWidth - newBox.width;
128563                     break;
128564                 case 'west':
128565                     newBox.x -= snappedWidth - newBox.width;
128566                     break;
128567                 case 'northwest':
128568                     newBox.x -= snappedWidth - newBox.width;
128569                     newBox.y -= snappedHeight - newBox.height;
128570             }
128571             newBox.width = snappedWidth;
128572             newBox.height = snappedHeight;
128573         }
128574
128575         // out of bounds
128576         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
128577             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
128578
128579             // Re-adjust the X position if we were dragging the west side
128580             if (adjustX) {
128581                 newBox.x = box.x + (box.width - newBox.width);
128582             }
128583         } else {
128584             me.lastX = newBox.x;
128585         }
128586         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
128587             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
128588
128589             // Re-adjust the Y position if we were dragging the north side
128590             if (adjustY) {
128591                 newBox.y = box.y + (box.height - newBox.height);
128592             }
128593         } else {
128594             me.lastY = newBox.y;
128595         }
128596
128597         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
128598         if (me.preserveRatio || e.shiftKey) {
128599             var newHeight,
128600                 newWidth;
128601
128602             ratio = me.startBox.width / me.startBox.height;
128603
128604             // Calculate aspect ratio constrained values.
128605             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
128606             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
128607
128608             // X axis: width-only change, height must obey
128609             if (axis == 1) {
128610                 newBox.height = newHeight;
128611             }
128612
128613             // Y axis: height-only change, width must obey
128614             else if (axis == 2) {
128615                 newBox.width = newWidth;
128616             }
128617
128618             // Corner drag.
128619             else {
128620                 // Drag ratio is the ratio of the mouse point from the opposite corner.
128621                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
128622                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
128623
128624                 // If drag ratio > aspect ratio then width is dominant and height must obey
128625                 if (dragRatio > ratio) {
128626                     newBox.height = newHeight;
128627                 } else {
128628                     newBox.width = newWidth;
128629                 }
128630
128631                 // Handle dragging start coordinates
128632                 if (region == 'northeast') {
128633                     newBox.y = box.y - (newBox.height - box.height);
128634                 } else if (region == 'northwest') {
128635                     newBox.y = box.y - (newBox.height - box.height);
128636                     newBox.x = box.x - (newBox.width - box.width);
128637                 } else if (region == 'southwest') {
128638                     newBox.x = box.x - (newBox.width - box.width);
128639                 }
128640             }
128641         }
128642
128643         if (heightAdjust === 0) {
128644             vertDir = 'none';
128645         }
128646         if (widthAdjust === 0) {
128647             horizDir = 'none';
128648         }
128649         me.resize(newBox, {
128650             horizontal: horizDir,
128651             vertical: vertDir
128652         }, atEnd);
128653     },
128654
128655     getResizeTarget: function(atEnd) {
128656         return atEnd ? this.target : this.getDynamicTarget();
128657     },
128658
128659     resize: function(box, direction, atEnd) {
128660         var target = this.getResizeTarget(atEnd);
128661         if (target.isComponent) {
128662             if (target.floating) {
128663                 target.setPagePosition(box.x, box.y);
128664             }
128665             target.setSize(box.width, box.height);
128666         } else {
128667             target.setBox(box);
128668             // update the originalTarget if this was wrapped.
128669             if (this.originalTarget) {
128670                 this.originalTarget.setBox(box);
128671             }
128672         }
128673     },
128674
128675     onEnd: function(e) {
128676         this.updateDimensions(e, true);
128677         if (this.proxy) {
128678             this.proxy.hide();
128679         }
128680     }
128681 });
128682
128683 /**
128684  * @class Ext.resizer.SplitterTracker
128685  * @extends Ext.dd.DragTracker
128686  * Private utility class for Ext.Splitter.
128687  * @private
128688  */
128689 Ext.define('Ext.resizer.SplitterTracker', {
128690     extend: 'Ext.dd.DragTracker',
128691     requires: ['Ext.util.Region'],
128692     enabled: true,
128693     
128694     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
128695
128696     getPrevCmp: function() {
128697         var splitter = this.getSplitter();
128698         return splitter.previousSibling();
128699     },
128700
128701     getNextCmp: function() {
128702         var splitter = this.getSplitter();
128703         return splitter.nextSibling();
128704     },
128705
128706     // ensure the tracker is enabled, store boxes of previous and next
128707     // components and calculate the constrain region
128708     onBeforeStart: function(e) {
128709         var me = this,
128710             prevCmp = me.getPrevCmp(),
128711             nextCmp = me.getNextCmp(),
128712             collapseEl = me.getSplitter().collapseEl,
128713             overlay;
128714             
128715         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
128716             return false;
128717         }
128718
128719         // SplitterTracker is disabled if any of its adjacents are collapsed.
128720         if (nextCmp.collapsed || prevCmp.collapsed) {
128721             return false;
128722         }
128723         
128724         overlay = me.overlay =  Ext.getBody().createChild({
128725             cls: me.overlayCls, 
128726             html: '&#160;'
128727         });
128728         overlay.unselectable();
128729         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
128730         overlay.show();
128731         
128732         // store boxes of previous and next
128733         me.prevBox  = prevCmp.getEl().getBox();
128734         me.nextBox  = nextCmp.getEl().getBox();
128735         me.constrainTo = me.calculateConstrainRegion();
128736     },
128737
128738     // We move the splitter el. Add the proxy class.
128739     onStart: function(e) {
128740         var splitter = this.getSplitter();
128741         splitter.addCls(splitter.baseCls + '-active');
128742     },
128743
128744     // calculate the constrain Region in which the splitter el may be moved.
128745     calculateConstrainRegion: function() {
128746         var me         = this,
128747             splitter   = me.getSplitter(),
128748             splitWidth = splitter.getWidth(),
128749             defaultMin = splitter.defaultSplitMin,
128750             orient     = splitter.orientation,
128751             prevBox    = me.prevBox,
128752             prevCmp    = me.getPrevCmp(),
128753             nextBox    = me.nextBox,
128754             nextCmp    = me.getNextCmp(),
128755             // prev and nextConstrainRegions are the maximumBoxes minus the
128756             // minimumBoxes. The result is always the intersection
128757             // of these two boxes.
128758             prevConstrainRegion, nextConstrainRegion;
128759
128760         // vertical splitters, so resizing left to right
128761         if (orient === 'vertical') {
128762
128763             // Region constructor accepts (top, right, bottom, left)
128764             // anchored/calculated from the left
128765             prevConstrainRegion = Ext.create('Ext.util.Region',
128766                 prevBox.y,
128767                 // Right boundary is x + maxWidth if there IS a maxWidth.
128768                 // Otherwise it is calculated based upon the minWidth of the next Component
128769                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
128770                 prevBox.bottom,
128771                 prevBox.x + (prevCmp.minWidth || defaultMin)
128772             );
128773             // anchored/calculated from the right
128774             nextConstrainRegion = Ext.create('Ext.util.Region',
128775                 nextBox.y,
128776                 nextBox.right - (nextCmp.minWidth || defaultMin),
128777                 nextBox.bottom,
128778                 // Left boundary is right - maxWidth if there IS a maxWidth.
128779                 // Otherwise it is calculated based upon the minWidth of the previous Component
128780                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
128781             );
128782         } else {
128783             // anchored/calculated from the top
128784             prevConstrainRegion = Ext.create('Ext.util.Region',
128785                 prevBox.y + (prevCmp.minHeight || defaultMin),
128786                 prevBox.right,
128787                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
128788                 // Otherwise it is calculated based upon the minWidth of the next Component
128789                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
128790                 prevBox.x
128791             );
128792             // anchored/calculated from the bottom
128793             nextConstrainRegion = Ext.create('Ext.util.Region',
128794                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
128795                 // Otherwise it is calculated based upon the minHeight of the previous Component
128796                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
128797                 nextBox.right,
128798                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
128799                 nextBox.x
128800             );
128801         }
128802
128803         // intersection of the two regions to provide region draggable
128804         return prevConstrainRegion.intersect(nextConstrainRegion);
128805     },
128806
128807     // Performs the actual resizing of the previous and next components
128808     performResize: function(e) {
128809         var me       = this,
128810             offset   = me.getOffset('dragTarget'),
128811             splitter = me.getSplitter(),
128812             orient   = splitter.orientation,
128813             prevCmp  = me.getPrevCmp(),
128814             nextCmp  = me.getNextCmp(),
128815             owner    = splitter.ownerCt,
128816             layout   = owner.getLayout();
128817
128818         // Inhibit automatic container layout caused by setSize calls below.
128819         owner.suspendLayout = true;
128820
128821         if (orient === 'vertical') {
128822             if (prevCmp) {
128823                 if (!prevCmp.maintainFlex) {
128824                     delete prevCmp.flex;
128825                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
128826                 }
128827             }
128828             if (nextCmp) {
128829                 if (!nextCmp.maintainFlex) {
128830                     delete nextCmp.flex;
128831                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
128832                 }
128833             }
128834         // verticals
128835         } else {
128836             if (prevCmp) {
128837                 if (!prevCmp.maintainFlex) {
128838                     delete prevCmp.flex;
128839                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
128840                 }
128841             }
128842             if (nextCmp) {
128843                 if (!nextCmp.maintainFlex) {
128844                     delete nextCmp.flex;
128845                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
128846                 }
128847             }
128848         }
128849         delete owner.suspendLayout;
128850         layout.onLayout();
128851     },
128852
128853     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
128854     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
128855     // regardless (by onBeforeStart).
128856     endDrag: function () {
128857         var me = this;
128858
128859         if (me.overlay) {
128860              me.overlay.remove();
128861              delete me.overlay;
128862         }
128863
128864         me.callParent(arguments); // this calls onEnd
128865     },
128866
128867     // perform the resize and remove the proxy class from the splitter el
128868     onEnd: function(e) {
128869         var me = this,
128870             splitter = me.getSplitter();
128871             
128872         splitter.removeCls(splitter.baseCls + '-active');
128873         me.performResize();
128874     },
128875
128876     // Track the proxy and set the proper XY coordinates
128877     // while constraining the drag
128878     onDrag: function(e) {
128879         var me        = this,
128880             offset    = me.getOffset('dragTarget'),
128881             splitter  = me.getSplitter(),
128882             splitEl   = splitter.getEl(),
128883             orient    = splitter.orientation;
128884
128885         if (orient === "vertical") {
128886             splitEl.setX(me.startRegion.left + offset[0]);
128887         } else {
128888             splitEl.setY(me.startRegion.top + offset[1]);
128889         }
128890     },
128891
128892     getSplitter: function() {
128893         return Ext.getCmp(this.getDragCt().id);
128894     }
128895 });
128896 /**
128897  * @class Ext.selection.CellModel
128898  * @extends Ext.selection.Model
128899  */
128900 Ext.define('Ext.selection.CellModel', {
128901     extend: 'Ext.selection.Model',
128902     alias: 'selection.cellmodel',
128903     requires: ['Ext.util.KeyNav'],
128904
128905     /**
128906      * @cfg {Boolean} enableKeyNav
128907      * Turns on/off keyboard navigation within the grid.
128908      */
128909     enableKeyNav: true,
128910
128911     /**
128912      * @cfg {Boolean} preventWrap
128913      * Set this configuration to true to prevent wrapping around of selection as
128914      * a user navigates to the first or last column.
128915      */
128916     preventWrap: false,
128917
128918     constructor: function(){
128919         this.addEvents(
128920             /**
128921              * @event deselect
128922              * Fired after a cell is deselected
128923              * @param {Ext.selection.CellModel} this
128924              * @param {Ext.data.Model} record The record of the deselected cell
128925              * @param {Number} row The row index deselected
128926              * @param {Number} column The column index deselected
128927              */
128928             'deselect',
128929
128930             /**
128931              * @event select
128932              * Fired after a cell is selected
128933              * @param {Ext.selection.CellModel} this
128934              * @param {Ext.data.Model} record The record of the selected cell
128935              * @param {Number} row The row index selected
128936              * @param {Number} column The column index selected
128937              */
128938             'select'
128939         );
128940         this.callParent(arguments);
128941     },
128942
128943     bindComponent: function(view) {
128944         var me = this;
128945         me.primaryView = view;
128946         me.views = me.views || [];
128947         me.views.push(view);
128948         me.bind(view.getStore(), true);
128949
128950         view.on({
128951             cellmousedown: me.onMouseDown,
128952             refresh: me.onViewRefresh,
128953             scope: me
128954         });
128955
128956         if (me.enableKeyNav) {
128957             me.initKeyNav(view);
128958         }
128959     },
128960
128961     initKeyNav: function(view) {
128962         var me = this;
128963
128964         if (!view.rendered) {
128965             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128966             return;
128967         }
128968
128969         view.el.set({
128970             tabIndex: -1
128971         });
128972
128973         // view.el has tabIndex -1 to allow for
128974         // keyboard events to be passed to it.
128975         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
128976             up: me.onKeyUp,
128977             down: me.onKeyDown,
128978             right: me.onKeyRight,
128979             left: me.onKeyLeft,
128980             tab: me.onKeyTab,
128981             scope: me
128982         });
128983     },
128984
128985     getHeaderCt: function() {
128986         return this.primaryView.headerCt;
128987     },
128988
128989     onKeyUp: function(e, t) {
128990         this.move('up', e);
128991     },
128992
128993     onKeyDown: function(e, t) {
128994         this.move('down', e);
128995     },
128996
128997     onKeyLeft: function(e, t) {
128998         this.move('left', e);
128999     },
129000
129001     onKeyRight: function(e, t) {
129002         this.move('right', e);
129003     },
129004
129005     move: function(dir, e) {
129006         var me = this,
129007             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
129008         if (pos) {
129009             me.setCurrentPosition(pos);
129010         }
129011         return pos;
129012     },
129013
129014     /**
129015      * Returns the current position in the format {row: row, column: column}
129016      */
129017     getCurrentPosition: function() {
129018         return this.position;
129019     },
129020
129021     /**
129022      * Sets the current position
129023      * @param {Object} position The position to set.
129024      */
129025     setCurrentPosition: function(pos) {
129026         var me = this;
129027
129028         if (me.position) {
129029             me.onCellDeselect(me.position);
129030         }
129031         if (pos) {
129032             me.onCellSelect(pos);
129033         }
129034         me.position = pos;
129035     },
129036
129037     /**
129038      * Set the current position based on where the user clicks.
129039      * @private
129040      */
129041     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
129042         this.setCurrentPosition({
129043             row: rowIndex,
129044             column: cellIndex
129045         });
129046     },
129047
129048     // notify the view that the cell has been selected to update the ui
129049     // appropriately and bring the cell into focus
129050     onCellSelect: function(position) {
129051         var me = this,
129052             store = me.view.getStore(),
129053             record = store.getAt(position.row);
129054
129055         me.doSelect(record);
129056         me.primaryView.onCellSelect(position);
129057         // TODO: Remove temporary cellFocus call here.
129058         me.primaryView.onCellFocus(position);
129059         me.fireEvent('select', me, record, position.row, position.column);
129060     },
129061
129062     // notify view that the cell has been deselected to update the ui
129063     // appropriately
129064     onCellDeselect: function(position) {
129065         var me = this,
129066             store = me.view.getStore(),
129067             record = store.getAt(position.row);
129068
129069         me.doDeselect(record);
129070         me.primaryView.onCellDeselect(position);
129071         me.fireEvent('deselect', me, record, position.row, position.column);
129072     },
129073
129074     onKeyTab: function(e, t) {
129075         var me = this,
129076             direction = e.shiftKey ? 'left' : 'right',
129077             editingPlugin = me.view.editingPlugin,
129078             position = me.move(direction, e);
129079
129080         if (editingPlugin && position && me.wasEditing) {
129081             editingPlugin.startEditByPosition(position);
129082         }
129083         delete me.wasEditing;
129084     },
129085
129086     onEditorTab: function(editingPlugin, e) {
129087         var me = this,
129088             direction = e.shiftKey ? 'left' : 'right',
129089             position  = me.move(direction, e);
129090
129091         if (position) {
129092             editingPlugin.startEditByPosition(position);
129093             me.wasEditing = true;
129094         }
129095     },
129096
129097     refresh: function() {
129098         var pos = this.getCurrentPosition();
129099         if (pos) {
129100             this.onCellSelect(pos);
129101         }
129102     },
129103
129104     onViewRefresh: function() {
129105         var pos = this.getCurrentPosition();
129106         if (pos) {
129107             this.onCellDeselect(pos);
129108             this.setCurrentPosition(null);
129109         }
129110     },
129111
129112     selectByPosition: function(position) {
129113         this.setCurrentPosition(position);
129114     }
129115 });
129116 /**
129117  * @class Ext.selection.RowModel
129118  * @extends Ext.selection.Model
129119  */
129120 Ext.define('Ext.selection.RowModel', {
129121     extend: 'Ext.selection.Model',
129122     alias: 'selection.rowmodel',
129123     requires: ['Ext.util.KeyNav'],
129124
129125     /**
129126      * @private
129127      * Number of pixels to scroll to the left/right when pressing
129128      * left/right keys.
129129      */
129130     deltaScroll: 5,
129131
129132     /**
129133      * @cfg {Boolean} enableKeyNav
129134      *
129135      * Turns on/off keyboard navigation within the grid.
129136      */
129137     enableKeyNav: true,
129138     
129139     /**
129140      * @cfg {Boolean} [ignoreRightMouseSelection=true]
129141      * True to ignore selections that are made when using the right mouse button if there are
129142      * records that are already selected. If no records are selected, selection will continue 
129143      * as normal
129144      */
129145     ignoreRightMouseSelection: true,
129146
129147     constructor: function(){
129148         this.addEvents(
129149             /**
129150              * @event beforedeselect
129151              * Fired before a record is deselected. If any listener returns false, the
129152              * deselection is cancelled.
129153              * @param {Ext.selection.RowModel} this
129154              * @param {Ext.data.Model} record The deselected record
129155              * @param {Number} index The row index deselected
129156              */
129157             'beforedeselect',
129158
129159             /**
129160              * @event beforeselect
129161              * Fired before a record is selected. If any listener returns false, the
129162              * selection is cancelled.
129163              * @param {Ext.selection.RowModel} this
129164              * @param {Ext.data.Model} record The selected record
129165              * @param {Number} index The row index selected
129166              */
129167             'beforeselect',
129168
129169             /**
129170              * @event deselect
129171              * Fired after a record is deselected
129172              * @param {Ext.selection.RowModel} this
129173              * @param {Ext.data.Model} record The deselected record
129174              * @param {Number} index The row index deselected
129175              */
129176             'deselect',
129177
129178             /**
129179              * @event select
129180              * Fired after a record is selected
129181              * @param {Ext.selection.RowModel} this
129182              * @param {Ext.data.Model} record The selected record
129183              * @param {Number} index The row index selected
129184              */
129185             'select'
129186         );
129187         this.callParent(arguments);
129188     },
129189
129190     bindComponent: function(view) {
129191         var me = this;
129192
129193         me.views = me.views || [];
129194         me.views.push(view);
129195         me.bind(view.getStore(), true);
129196
129197         view.on({
129198             itemmousedown: me.onRowMouseDown,
129199             scope: me
129200         });
129201
129202         if (me.enableKeyNav) {
129203             me.initKeyNav(view);
129204         }
129205     },
129206
129207     initKeyNav: function(view) {
129208         var me = this;
129209
129210         if (!view.rendered) {
129211             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
129212             return;
129213         }
129214
129215         view.el.set({
129216             tabIndex: -1
129217         });
129218
129219         // view.el has tabIndex -1 to allow for
129220         // keyboard events to be passed to it.
129221         me.keyNav = new Ext.util.KeyNav(view.el, {
129222             up: me.onKeyUp,
129223             down: me.onKeyDown,
129224             right: me.onKeyRight,
129225             left: me.onKeyLeft,
129226             pageDown: me.onKeyPageDown,
129227             pageUp: me.onKeyPageUp,
129228             home: me.onKeyHome,
129229             end: me.onKeyEnd,
129230             scope: me
129231         });
129232         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
129233     },
129234
129235     // Returns the number of rows currently visible on the screen or
129236     // false if there were no rows. This assumes that all rows are
129237     // of the same height and the first view is accurate.
129238     getRowsVisible: function() {
129239         var rowsVisible = false,
129240             view = this.views[0],
129241             row = view.getNode(0),
129242             rowHeight, gridViewHeight;
129243
129244         if (row) {
129245             rowHeight = Ext.fly(row).getHeight();
129246             gridViewHeight = view.el.getHeight();
129247             rowsVisible = Math.floor(gridViewHeight / rowHeight);
129248         }
129249
129250         return rowsVisible;
129251     },
129252
129253     // go to last visible record in grid.
129254     onKeyEnd: function(e, t) {
129255         var me = this,
129256             last = me.store.getAt(me.store.getCount() - 1);
129257
129258         if (last) {
129259             if (e.shiftKey) {
129260                 me.selectRange(last, me.lastFocused || 0);
129261                 me.setLastFocused(last);
129262             } else if (e.ctrlKey) {
129263                 me.setLastFocused(last);
129264             } else {
129265                 me.doSelect(last);
129266             }
129267         }
129268     },
129269
129270     // go to first visible record in grid.
129271     onKeyHome: function(e, t) {
129272         var me = this,
129273             first = me.store.getAt(0);
129274
129275         if (first) {
129276             if (e.shiftKey) {
129277                 me.selectRange(first, me.lastFocused || 0);
129278                 me.setLastFocused(first);
129279             } else if (e.ctrlKey) {
129280                 me.setLastFocused(first);
129281             } else {
129282                 me.doSelect(first, false);
129283             }
129284         }
129285     },
129286
129287     // Go one page up from the lastFocused record in the grid.
129288     onKeyPageUp: function(e, t) {
129289         var me = this,
129290             rowsVisible = me.getRowsVisible(),
129291             selIdx,
129292             prevIdx,
129293             prevRecord,
129294             currRec;
129295
129296         if (rowsVisible) {
129297             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
129298             prevIdx = selIdx - rowsVisible;
129299             if (prevIdx < 0) {
129300                 prevIdx = 0;
129301             }
129302             prevRecord = me.store.getAt(prevIdx);
129303             if (e.shiftKey) {
129304                 currRec = me.store.getAt(selIdx);
129305                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
129306                 me.setLastFocused(prevRecord);
129307             } else if (e.ctrlKey) {
129308                 e.preventDefault();
129309                 me.setLastFocused(prevRecord);
129310             } else {
129311                 me.doSelect(prevRecord);
129312             }
129313
129314         }
129315     },
129316
129317     // Go one page down from the lastFocused record in the grid.
129318     onKeyPageDown: function(e, t) {
129319         var me = this,
129320             rowsVisible = me.getRowsVisible(),
129321             selIdx,
129322             nextIdx,
129323             nextRecord,
129324             currRec;
129325
129326         if (rowsVisible) {
129327             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
129328             nextIdx = selIdx + rowsVisible;
129329             if (nextIdx >= me.store.getCount()) {
129330                 nextIdx = me.store.getCount() - 1;
129331             }
129332             nextRecord = me.store.getAt(nextIdx);
129333             if (e.shiftKey) {
129334                 currRec = me.store.getAt(selIdx);
129335                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
129336                 me.setLastFocused(nextRecord);
129337             } else if (e.ctrlKey) {
129338                 // some browsers, this means go thru browser tabs
129339                 // attempt to stop.
129340                 e.preventDefault();
129341                 me.setLastFocused(nextRecord);
129342             } else {
129343                 me.doSelect(nextRecord);
129344             }
129345         }
129346     },
129347
129348     // Select/Deselect based on pressing Spacebar.
129349     // Assumes a SIMPLE selectionmode style
129350     onKeyPress: function(e, t) {
129351         if (e.getKey() === e.SPACE) {
129352             e.stopEvent();
129353             var me = this,
129354                 record = me.lastFocused;
129355
129356             if (record) {
129357                 if (me.isSelected(record)) {
129358                     me.doDeselect(record, false);
129359                 } else {
129360                     me.doSelect(record, true);
129361                 }
129362             }
129363         }
129364     },
129365
129366     // Navigate one record up. This could be a selection or
129367     // could be simply focusing a record for discontiguous
129368     // selection. Provides bounds checking.
129369     onKeyUp: function(e, t) {
129370         var me = this,
129371             view = me.views[0],
129372             idx  = me.store.indexOf(me.lastFocused),
129373             record;
129374
129375         if (idx > 0) {
129376             // needs to be the filtered count as thats what
129377             // will be visible.
129378             record = me.store.getAt(idx - 1);
129379             if (e.shiftKey && me.lastFocused) {
129380                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
129381                     me.doDeselect(me.lastFocused, true);
129382                     me.setLastFocused(record);
129383                 } else if (!me.isSelected(me.lastFocused)) {
129384                     me.doSelect(me.lastFocused, true);
129385                     me.doSelect(record, true);
129386                 } else {
129387                     me.doSelect(record, true);
129388                 }
129389             } else if (e.ctrlKey) {
129390                 me.setLastFocused(record);
129391             } else {
129392                 me.doSelect(record);
129393                 //view.focusRow(idx - 1);
129394             }
129395         }
129396         // There was no lastFocused record, and the user has pressed up
129397         // Ignore??
129398         //else if (this.selected.getCount() == 0) {
129399         //
129400         //    this.doSelect(record);
129401         //    //view.focusRow(idx - 1);
129402         //}
129403     },
129404
129405     // Navigate one record down. This could be a selection or
129406     // could be simply focusing a record for discontiguous
129407     // selection. Provides bounds checking.
129408     onKeyDown: function(e, t) {
129409         var me = this,
129410             view = me.views[0],
129411             idx  = me.store.indexOf(me.lastFocused),
129412             record;
129413
129414         // needs to be the filtered count as thats what
129415         // will be visible.
129416         if (idx + 1 < me.store.getCount()) {
129417             record = me.store.getAt(idx + 1);
129418             if (me.selected.getCount() === 0) {
129419                 me.doSelect(record);
129420                 //view.focusRow(idx + 1);
129421             } else if (e.shiftKey && me.lastFocused) {
129422                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
129423                     me.doDeselect(me.lastFocused, true);
129424                     me.setLastFocused(record);
129425                 } else if (!me.isSelected(me.lastFocused)) {
129426                     me.doSelect(me.lastFocused, true);
129427                     me.doSelect(record, true);
129428                 } else {
129429                     me.doSelect(record, true);
129430                 }
129431             } else if (e.ctrlKey) {
129432                 me.setLastFocused(record);
129433             } else {
129434                 me.doSelect(record);
129435                 //view.focusRow(idx + 1);
129436             }
129437         }
129438     },
129439
129440     scrollByDeltaX: function(delta) {
129441         var view    = this.views[0],
129442             section = view.up(),
129443             hScroll = section.horizontalScroller;
129444
129445         if (hScroll) {
129446             hScroll.scrollByDeltaX(delta);
129447         }
129448     },
129449
129450     onKeyLeft: function(e, t) {
129451         this.scrollByDeltaX(-this.deltaScroll);
129452     },
129453
129454     onKeyRight: function(e, t) {
129455         this.scrollByDeltaX(this.deltaScroll);
129456     },
129457
129458     // Select the record with the event included so that
129459     // we can take into account ctrlKey, shiftKey, etc
129460     onRowMouseDown: function(view, record, item, index, e) {
129461         view.el.focus();
129462         if (!this.allowRightMouseSelection(e)) {
129463             return;
129464         }
129465         this.selectWithEvent(record, e);
129466     },
129467     
129468     /**
129469      * Checks whether a selection should proceed based on the ignoreRightMouseSelection
129470      * option.
129471      * @private
129472      * @param {Ext.EventObject} e The event
129473      * @return {Boolean} False if the selection should not proceed
129474      */
129475     allowRightMouseSelection: function(e) {
129476         var disallow = this.ignoreRightMouseSelection && e.button !== 0;
129477         if (disallow) {
129478             disallow = this.hasSelection();
129479         }
129480         return !disallow;
129481     },
129482
129483     // Allow the GridView to update the UI by
129484     // adding/removing a CSS class from the row.
129485     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
129486         var me      = this,
129487             views   = me.views,
129488             viewsLn = views.length,
129489             store   = me.store,
129490             rowIdx  = store.indexOf(record),
129491             eventName = isSelected ? 'select' : 'deselect',
129492             i = 0;
129493
129494         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
129495                 commitFn() !== false) {
129496
129497             for (; i < viewsLn; i++) {
129498                 if (isSelected) {
129499                     views[i].onRowSelect(rowIdx, suppressEvent);
129500                 } else {
129501                     views[i].onRowDeselect(rowIdx, suppressEvent);
129502                 }
129503             }
129504
129505             if (!suppressEvent) {
129506                 me.fireEvent(eventName, me, record, rowIdx);
129507             }
129508         }
129509     },
129510
129511     // Provide indication of what row was last focused via
129512     // the gridview.
129513     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
129514         var views   = this.views,
129515             viewsLn = views.length,
129516             store   = this.store,
129517             rowIdx,
129518             i = 0;
129519
129520         if (oldFocused) {
129521             rowIdx = store.indexOf(oldFocused);
129522             if (rowIdx != -1) {
129523                 for (; i < viewsLn; i++) {
129524                     views[i].onRowFocus(rowIdx, false);
129525                 }
129526             }
129527         }
129528
129529         if (newFocused) {
129530             rowIdx = store.indexOf(newFocused);
129531             if (rowIdx != -1) {
129532                 for (i = 0; i < viewsLn; i++) {
129533                     views[i].onRowFocus(rowIdx, true, supressFocus);
129534                 }
129535             }
129536         }
129537     },
129538
129539     onEditorTab: function(editingPlugin, e) {
129540         var me = this,
129541             view = me.views[0],
129542             record = editingPlugin.getActiveRecord(),
129543             header = editingPlugin.getActiveColumn(),
129544             position = view.getPosition(record, header),
129545             direction = e.shiftKey ? 'left' : 'right',
129546             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
129547
129548         if (newPosition) {
129549             editingPlugin.startEditByPosition(newPosition);
129550         }
129551     },
129552
129553     selectByPosition: function(position) {
129554         var record = this.store.getAt(position.row);
129555         this.select(record);
129556     }
129557 });
129558 /**
129559  * @class Ext.selection.CheckboxModel
129560  * @extends Ext.selection.RowModel
129561  *
129562  * A selection model that renders a column of checkboxes that can be toggled to
129563  * select or deselect rows. The default mode for this selection model is MULTI.
129564  *
129565  * The selection model will inject a header for the checkboxes in the first view
129566  * and according to the 'injectCheckbox' configuration.
129567  */
129568 Ext.define('Ext.selection.CheckboxModel', {
129569     alias: 'selection.checkboxmodel',
129570     extend: 'Ext.selection.RowModel',
129571
129572     /**
129573      * @cfg {String} mode
129574      * Modes of selection.
129575      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
129576      */
129577     mode: 'MULTI',
129578
129579     /**
129580      * @cfg {Number/Boolean/String} injectCheckbox
129581      * Instructs the SelectionModel whether or not to inject the checkbox header
129582      * automatically or not. (Note: By not placing the checkbox in manually, the
129583      * grid view will need to be rendered 2x on initial render.)
129584      * Supported values are a Number index, false and the strings 'first' and 'last'.
129585      */
129586     injectCheckbox: 0,
129587
129588     /**
129589      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
129590      * checkbox column.
129591      */
129592     checkOnly: false,
129593
129594     headerWidth: 24,
129595
129596     // private
129597     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
129598
129599     bindComponent: function(view) {
129600         var me = this;
129601
129602         me.sortable = false;
129603         me.callParent(arguments);
129604         if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
129605             // if we have a locked header, only hook up to the first
129606             view.headerCt.on('headerclick', me.onHeaderClick, me);
129607             me.addCheckbox(true);
129608             me.mon(view.ownerCt, 'reconfigure', me.addCheckbox, me);
129609         }
129610     },
129611
129612     hasLockedHeader: function(){
129613         var hasLocked = false;
129614         Ext.each(this.views, function(view){
129615             if (view.headerCt.lockedCt) {
129616                 hasLocked = true;
129617                 return false;
129618             }
129619         });
129620         return hasLocked;
129621     },
129622
129623     /**
129624      * Add the header checkbox to the header row
129625      * @private
129626      * @param {Boolean} initial True if we're binding for the first time.
129627      */
129628     addCheckbox: function(initial){
129629         var me = this,
129630             checkbox = me.injectCheckbox,
129631             view = me.views[0],
129632             headerCt = view.headerCt;
129633
129634         if (checkbox !== false) {
129635             if (checkbox == 'first') {
129636                 checkbox = 0;
129637             } else if (checkbox == 'last') {
129638                 checkbox = headerCt.getColumnCount();
129639             }
129640             headerCt.add(checkbox,  me.getHeaderConfig());
129641         }
129642
129643         if (initial !== true) {
129644             view.refresh();
129645         }
129646     },
129647
129648     /**
129649      * Toggle the ui header between checked and unchecked state.
129650      * @param {Boolean} isChecked
129651      * @private
129652      */
129653     toggleUiHeader: function(isChecked) {
129654         var view     = this.views[0],
129655             headerCt = view.headerCt,
129656             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
129657
129658         if (checkHd) {
129659             if (isChecked) {
129660                 checkHd.el.addCls(this.checkerOnCls);
129661             } else {
129662                 checkHd.el.removeCls(this.checkerOnCls);
129663             }
129664         }
129665     },
129666
129667     /**
129668      * Toggle between selecting all and deselecting all when clicking on
129669      * a checkbox header.
129670      */
129671     onHeaderClick: function(headerCt, header, e) {
129672         if (header.isCheckerHd) {
129673             e.stopEvent();
129674             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
129675             if (isChecked) {
129676                 // We have to supress the event or it will scrollTo the change
129677                 this.deselectAll(true);
129678             } else {
129679                 // We have to supress the event or it will scrollTo the change
129680                 this.selectAll(true);
129681             }
129682         }
129683     },
129684
129685     /**
129686      * Retrieve a configuration to be used in a HeaderContainer.
129687      * This should be used when injectCheckbox is set to false.
129688      */
129689     getHeaderConfig: function() {
129690         var me = this;
129691
129692         return {
129693             isCheckerHd: true,
129694             text : '&#160;',
129695             width: me.headerWidth,
129696             sortable: false,
129697             draggable: false,
129698             resizable: false,
129699             hideable: false,
129700             menuDisabled: true,
129701             dataIndex: '',
129702             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
129703             renderer: Ext.Function.bind(me.renderer, me),
129704             locked: me.hasLockedHeader()
129705         };
129706     },
129707
129708     /**
129709      * Generates the HTML to be rendered in the injected checkbox column for each row.
129710      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
129711      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
129712      */
129713     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
129714         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
129715         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
129716     },
129717
129718     // override
129719     onRowMouseDown: function(view, record, item, index, e) {
129720         view.el.focus();
129721         var me = this,
129722             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
129723             
129724         if (!me.allowRightMouseSelection(e)) {
129725             return;
129726         }
129727
129728         // checkOnly set, but we didn't click on a checker.
129729         if (me.checkOnly && !checker) {
129730             return;
129731         }
129732
129733         if (checker) {
129734             var mode = me.getSelectionMode();
129735             // dont change the mode if its single otherwise
129736             // we would get multiple selection
129737             if (mode !== 'SINGLE') {
129738                 me.setSelectionMode('SIMPLE');
129739             }
129740             me.selectWithEvent(record, e);
129741             me.setSelectionMode(mode);
129742         } else {
129743             me.selectWithEvent(record, e);
129744         }
129745     },
129746
129747     /**
129748      * Synchronize header checker value as selection changes.
129749      * @private
129750      */
129751     onSelectChange: function() {
129752         this.callParent(arguments);
129753
129754         // check to see if all records are selected
129755         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
129756         this.toggleUiHeader(hdSelectStatus);
129757     }
129758 });
129759
129760 /**
129761  * @class Ext.selection.TreeModel
129762  * @extends Ext.selection.RowModel
129763  *
129764  * Adds custom behavior for left/right keyboard navigation for use with a tree.
129765  * Depends on the view having an expand and collapse method which accepts a
129766  * record.
129767  * 
129768  * @private
129769  */
129770 Ext.define('Ext.selection.TreeModel', {
129771     extend: 'Ext.selection.RowModel',
129772     alias: 'selection.treemodel',
129773     
129774     // typically selection models prune records from the selection
129775     // model when they are removed, because the TreeView constantly
129776     // adds/removes records as they are expanded/collapsed
129777     pruneRemoved: false,
129778     
129779     onKeyRight: function(e, t) {
129780         var focused = this.getLastFocused(),
129781             view    = this.view;
129782             
129783         if (focused) {
129784             // tree node is already expanded, go down instead
129785             // this handles both the case where we navigate to firstChild and if
129786             // there are no children to the nextSibling
129787             if (focused.isExpanded()) {
129788                 this.onKeyDown(e, t);
129789             // if its not a leaf node, expand it
129790             } else if (!focused.isLeaf()) {
129791                 view.expand(focused);
129792             }
129793         }
129794     },
129795     
129796     onKeyLeft: function(e, t) {
129797         var focused = this.getLastFocused(),
129798             view    = this.view,
129799             viewSm  = view.getSelectionModel(),
129800             parentNode, parentRecord;
129801
129802         if (focused) {
129803             parentNode = focused.parentNode;
129804             // if focused node is already expanded, collapse it
129805             if (focused.isExpanded()) {
129806                 view.collapse(focused);
129807             // has a parentNode and its not root
129808             // TODO: this needs to cover the case where the root isVisible
129809             } else if (parentNode && !parentNode.isRoot()) {
129810                 // Select a range of records when doing multiple selection.
129811                 if (e.shiftKey) {
129812                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
129813                     viewSm.setLastFocused(parentNode);
129814                 // just move focus, not selection
129815                 } else if (e.ctrlKey) {
129816                     viewSm.setLastFocused(parentNode);
129817                 // select it
129818                 } else {
129819                     viewSm.select(parentNode);
129820                 }
129821             }
129822         }
129823     },
129824     
129825     onKeyPress: function(e, t) {
129826         var key = e.getKey(),
129827             selected, 
129828             checked;
129829         
129830         if (key === e.SPACE || key === e.ENTER) {
129831             e.stopEvent();
129832             selected = this.getLastSelected();
129833             if (selected) {
129834                 this.view.onCheckChange(selected);
129835             }
129836         } else {
129837             this.callParent(arguments);
129838         }
129839     }
129840 });
129841
129842 /**
129843  * @class Ext.slider.Thumb
129844  * @extends Ext.Base
129845  * @private
129846  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
129847  * be created internally by an {@link Ext.slider.Multi Multi slider}.
129848  */
129849 Ext.define('Ext.slider.Thumb', {
129850     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
129851     /**
129852      * @private
129853      * @property {Number} topThumbZIndex
129854      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
129855      */
129856     topZIndex: 10000,
129857
129858     /**
129859      * @cfg {Ext.slider.MultiSlider} slider (required)
129860      * The Slider to render to.
129861      */
129862
129863     /**
129864      * Creates new slider thumb.
129865      * @param {Object} config (optional) Config object.
129866      */
129867     constructor: function(config) {
129868         var me = this;
129869
129870         /**
129871          * @property {Ext.slider.MultiSlider} slider
129872          * The slider this thumb is contained within
129873          */
129874         Ext.apply(me, config || {}, {
129875             cls: Ext.baseCSSPrefix + 'slider-thumb',
129876
129877             /**
129878              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
129879              */
129880             constrain: false
129881         });
129882         me.callParent([config]);
129883
129884         if (me.slider.vertical) {
129885             Ext.apply(me, Ext.slider.Thumb.Vertical);
129886         }
129887     },
129888
129889     /**
129890      * Renders the thumb into a slider
129891      */
129892     render: function() {
129893         var me = this;
129894
129895         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
129896         if (me.disabled) {
129897             me.disable();
129898         }
129899         me.initEvents();
129900     },
129901
129902     /**
129903      * @private
129904      * move the thumb
129905      */
129906     move: function(v, animate){
129907         if(!animate){
129908             this.el.setLeft(v);
129909         }else{
129910             Ext.create('Ext.fx.Anim', {
129911                 target: this.el,
129912                 duration: 350,
129913                 to: {
129914                     left: v
129915                 }
129916             });
129917         }
129918     },
129919
129920     /**
129921      * @private
129922      * Bring thumb dom element to front.
129923      */
129924     bringToFront: function() {
129925         this.el.setStyle('zIndex', this.topZIndex);
129926     },
129927
129928     /**
129929      * @private
129930      * Send thumb dom element to back.
129931      */
129932     sendToBack: function() {
129933         this.el.setStyle('zIndex', '');
129934     },
129935
129936     /**
129937      * Enables the thumb if it is currently disabled
129938      */
129939     enable: function() {
129940         var me = this;
129941
129942         me.disabled = false;
129943         if (me.el) {
129944             me.el.removeCls(me.slider.disabledCls);
129945         }
129946     },
129947
129948     /**
129949      * Disables the thumb if it is currently enabled
129950      */
129951     disable: function() {
129952         var me = this;
129953
129954         me.disabled = true;
129955         if (me.el) {
129956             me.el.addCls(me.slider.disabledCls);
129957         }
129958     },
129959
129960     /**
129961      * Sets up an Ext.dd.DragTracker for this thumb
129962      */
129963     initEvents: function() {
129964         var me = this,
129965             el = me.el;
129966
129967         me.tracker = Ext.create('Ext.dd.DragTracker', {
129968             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
129969             onStart      : Ext.Function.bind(me.onDragStart, me),
129970             onDrag       : Ext.Function.bind(me.onDrag, me),
129971             onEnd        : Ext.Function.bind(me.onDragEnd, me),
129972             tolerance    : 3,
129973             autoStart    : 300,
129974             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
129975         });
129976
129977         me.tracker.initEl(el);
129978     },
129979
129980     /**
129981      * @private
129982      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
129983      * this returns false to disable the DragTracker too.
129984      * @return {Boolean} False if the slider is currently disabled
129985      */
129986     onBeforeDragStart : function(e) {
129987         if (this.disabled) {
129988             return false;
129989         } else {
129990             this.slider.promoteThumb(this);
129991             return true;
129992         }
129993     },
129994
129995     /**
129996      * @private
129997      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
129998      * to the thumb and fires the 'dragstart' event
129999      */
130000     onDragStart: function(e){
130001         var me = this;
130002
130003         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
130004         me.dragging = true;
130005         me.dragStartValue = me.value;
130006
130007         me.slider.fireEvent('dragstart', me.slider, e, me);
130008     },
130009
130010     /**
130011      * @private
130012      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
130013      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
130014      */
130015     onDrag: function(e) {
130016         var me       = this,
130017             slider   = me.slider,
130018             index    = me.index,
130019             newValue = me.getNewValue(),
130020             above,
130021             below;
130022
130023         if (me.constrain) {
130024             above = slider.thumbs[index + 1];
130025             below = slider.thumbs[index - 1];
130026
130027             if (below !== undefined && newValue <= below.value) {
130028                 newValue = below.value;
130029             }
130030
130031             if (above !== undefined && newValue >= above.value) {
130032                 newValue = above.value;
130033             }
130034         }
130035
130036         slider.setValue(index, newValue, false);
130037         slider.fireEvent('drag', slider, e, me);
130038     },
130039
130040     getNewValue: function() {
130041         var slider = this.slider,
130042             pos = slider.innerEl.translatePoints(this.tracker.getXY());
130043
130044         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
130045     },
130046
130047     /**
130048      * @private
130049      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
130050      * fires the 'changecomplete' event with the new value
130051      */
130052     onDragEnd: function(e) {
130053         var me     = this,
130054             slider = me.slider,
130055             value  = me.value;
130056
130057         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
130058
130059         me.dragging = false;
130060         slider.fireEvent('dragend', slider, e);
130061
130062         if (me.dragStartValue != value) {
130063             slider.fireEvent('changecomplete', slider, value, me);
130064         }
130065     },
130066
130067     destroy: function() {
130068         Ext.destroy(this.tracker);
130069     },
130070     statics: {
130071         // Method overrides to support vertical dragging of thumb within slider
130072         Vertical: {
130073             getNewValue: function() {
130074                 var slider   = this.slider,
130075                     innerEl  = slider.innerEl,
130076                     pos      = innerEl.translatePoints(this.tracker.getXY()),
130077                     bottom   = innerEl.getHeight() - pos.top;
130078
130079                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
130080             },
130081             move: function(v, animate) {
130082                 if (!animate) {
130083                     this.el.setBottom(v);
130084                 } else {
130085                     Ext.create('Ext.fx.Anim', {
130086                         target: this.el,
130087                         duration: 350,
130088                         to: {
130089                             bottom: v
130090                         }
130091                     });
130092                 }
130093             }
130094         }
130095     }
130096 });
130097
130098 /**
130099  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
130100  * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
130101  * options to the slider directly.
130102  *
130103  *     @example
130104  *     Ext.create('Ext.slider.Single', {
130105  *         width: 214,
130106  *         minValue: 0,
130107  *         maxValue: 100,
130108  *         useTips: true,
130109  *         renderTo: Ext.getBody()
130110  *     });
130111  *
130112  * Optionally provide your own tip text by passing tipText:
130113  *
130114  *     @example
130115  *     Ext.create('Ext.slider.Single', {
130116  *         width: 214,
130117  *         minValue: 0,
130118  *         maxValue: 100,
130119  *         useTips: true,
130120  *         tipText: function(thumb){
130121  *             return Ext.String.format('**{0}% complete**', thumb.value);
130122  *         },
130123  *         renderTo: Ext.getBody()
130124  *     });
130125  */
130126 Ext.define('Ext.slider.Tip', {
130127     extend: 'Ext.tip.Tip',
130128     minWidth: 10,
130129     alias: 'widget.slidertip',
130130     offsets : [0, -10],
130131
130132     isSliderTip: true,
130133
130134     init: function(slider) {
130135         var me = this;
130136
130137         slider.on({
130138             scope    : me,
130139             dragstart: me.onSlide,
130140             drag     : me.onSlide,
130141             dragend  : me.hide,
130142             destroy  : me.destroy
130143         });
130144     },
130145     /**
130146      * @private
130147      * Called whenever a dragstart or drag event is received on the associated Thumb.
130148      * Aligns the Tip with the Thumb's new position.
130149      * @param {Ext.slider.MultiSlider} slider The slider
130150      * @param {Ext.EventObject} e The Event object
130151      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
130152      */
130153     onSlide : function(slider, e, thumb) {
130154         var me = this;
130155         me.show();
130156         me.update(me.getText(thumb));
130157         me.doComponentLayout();
130158         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
130159     },
130160
130161     /**
130162      * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
130163      * Thumb that the Tip is attached to. Override to customize.
130164      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
130165      * @return {String} The text to display in the tip
130166      */
130167     getText : function(thumb) {
130168         return String(thumb.value);
130169     }
130170 });
130171 /**
130172  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
130173  * and animation. Can be added as an item to any container.
130174  *
130175  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
130176  *
130177  *     @example
130178  *     Ext.create('Ext.slider.Multi', {
130179  *         width: 200,
130180  *         values: [25, 50, 75],
130181  *         increment: 5,
130182  *         minValue: 0,
130183  *         maxValue: 100,
130184  *
130185  *         // this defaults to true, setting to false allows the thumbs to pass each other
130186  *         constrainThumbs: false,
130187  *         renderTo: Ext.getBody()
130188  *     });
130189  */
130190 Ext.define('Ext.slider.Multi', {
130191     extend: 'Ext.form.field.Base',
130192     alias: 'widget.multislider',
130193     alternateClassName: 'Ext.slider.MultiSlider',
130194
130195     requires: [
130196         'Ext.slider.Thumb',
130197         'Ext.slider.Tip',
130198         'Ext.Number',
130199         'Ext.util.Format',
130200         'Ext.Template',
130201         'Ext.layout.component.field.Slider'
130202     ],
130203
130204     // note: {id} here is really {inputId}, but {cmpId} is available
130205     fieldSubTpl: [
130206         '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
130207             '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
130208                 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
130209                     '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
130210                 '</div>',
130211             '</div>',
130212         '</div>',
130213         {
130214             disableFormats: true,
130215             compiled: true
130216         }
130217     ],
130218
130219     /**
130220      * @cfg {Number} value
130221      * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
130222      * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
130223      */
130224
130225     /**
130226      * @cfg {Number[]} values
130227      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
130228      * in this array. This will take precedence over the single {@link #value} config.
130229      */
130230
130231     /**
130232      * @cfg {Boolean} vertical
130233      * Orient the Slider vertically rather than horizontally.
130234      */
130235     vertical: false,
130236
130237     /**
130238      * @cfg {Number} minValue
130239      * The minimum value for the Slider.
130240      */
130241     minValue: 0,
130242
130243     /**
130244      * @cfg {Number} maxValue
130245      * The maximum value for the Slider.
130246      */
130247     maxValue: 100,
130248
130249     /**
130250      * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
130251      *
130252      * To disable rounding, configure as **false**.
130253      */
130254     decimalPrecision: 0,
130255
130256     /**
130257      * @cfg {Number} keyIncrement
130258      * How many units to change the Slider when adjusting with keyboard navigation. If the increment
130259      * config is larger, it will be used instead.
130260      */
130261     keyIncrement: 1,
130262
130263     /**
130264      * @cfg {Number} increment
130265      * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
130266      */
130267     increment: 0,
130268
130269     /**
130270      * @private
130271      * @property {Number[]} clickRange
130272      * 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],
130273      * 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'
130274      * 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
130275      */
130276     clickRange: [5,15],
130277
130278     /**
130279      * @cfg {Boolean} clickToChange
130280      * Determines whether or not clicking on the Slider axis will change the slider.
130281      */
130282     clickToChange : true,
130283
130284     /**
130285      * @cfg {Boolean} animate
130286      * Turn on or off animation.
130287      */
130288     animate: true,
130289
130290     /**
130291      * @property {Boolean} dragging
130292      * True while the thumb is in a drag operation
130293      */
130294     dragging: false,
130295
130296     /**
130297      * @cfg {Boolean} constrainThumbs
130298      * True to disallow thumbs from overlapping one another.
130299      */
130300     constrainThumbs: true,
130301
130302     componentLayout: 'sliderfield',
130303
130304     /**
130305      * @cfg {Boolean} useTips
130306      * True to use an Ext.slider.Tip to display tips for the value.
130307      */
130308     useTips : true,
130309
130310     /**
130311      * @cfg {Function} tipText
130312      * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
130313      * plugin.
130314      */
130315     tipText : null,
130316
130317     ariaRole: 'slider',
130318
130319     // private override
130320     initValue: function() {
130321         var me = this,
130322             extValue = Ext.value,
130323             // Fallback for initial values: values config -> value config -> minValue config -> 0
130324             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
130325             i = 0,
130326             len = values.length;
130327
130328         // Store for use in dirty check
130329         me.originalValue = values;
130330
130331         // Add a thumb for each value
130332         for (; i < len; i++) {
130333             me.addThumb(values[i]);
130334         }
130335     },
130336
130337     // private override
130338     initComponent : function() {
130339         var me = this,
130340             tipPlug,
130341             hasTip;
130342
130343         /**
130344          * @property {Array} thumbs
130345          * Array containing references to each thumb
130346          */
130347         me.thumbs = [];
130348
130349         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
130350
130351         me.addEvents(
130352             /**
130353              * @event beforechange
130354              * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
130355              * event and prevent the slider from changing.
130356              * @param {Ext.slider.Multi} slider The slider
130357              * @param {Number} newValue The new value which the slider is being changed to.
130358              * @param {Number} oldValue The old value which the slider was previously.
130359              */
130360             'beforechange',
130361
130362             /**
130363              * @event change
130364              * Fires when the slider value is changed.
130365              * @param {Ext.slider.Multi} slider The slider
130366              * @param {Number} newValue The new value which the slider has been changed to.
130367              * @param {Ext.slider.Thumb} thumb The thumb that was changed
130368              */
130369             'change',
130370
130371             /**
130372              * @event changecomplete
130373              * Fires when the slider value is changed by the user and any drag operations have completed.
130374              * @param {Ext.slider.Multi} slider The slider
130375              * @param {Number} newValue The new value which the slider has been changed to.
130376              * @param {Ext.slider.Thumb} thumb The thumb that was changed
130377              */
130378             'changecomplete',
130379
130380             /**
130381              * @event dragstart
130382              * Fires after a drag operation has started.
130383              * @param {Ext.slider.Multi} slider The slider
130384              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
130385              */
130386             'dragstart',
130387
130388             /**
130389              * @event drag
130390              * Fires continuously during the drag operation while the mouse is moving.
130391              * @param {Ext.slider.Multi} slider The slider
130392              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
130393              */
130394             'drag',
130395
130396             /**
130397              * @event dragend
130398              * Fires after the drag operation has completed.
130399              * @param {Ext.slider.Multi} slider The slider
130400              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
130401              */
130402             'dragend'
130403         );
130404
130405         if (me.vertical) {
130406             Ext.apply(me, Ext.slider.Multi.Vertical);
130407         }
130408
130409         me.callParent();
130410
130411         // only can use it if it exists.
130412         if (me.useTips) {
130413             tipPlug = me.tipText ? {getText: me.tipText} : {};
130414             me.plugins = me.plugins || [];
130415             Ext.each(me.plugins, function(plug){
130416                 if (plug.isSliderTip) {
130417                     hasTip = true;
130418                     return false;
130419                 }
130420             });
130421             if (!hasTip) {
130422                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
130423             }
130424         }
130425     },
130426
130427     /**
130428      * Creates a new thumb and adds it to the slider
130429      * @param {Number} value The initial value to set on the thumb. Defaults to 0
130430      * @return {Ext.slider.Thumb} The thumb
130431      */
130432     addThumb: function(value) {
130433         var me = this,
130434             thumb = Ext.create('Ext.slider.Thumb', {
130435             value    : value,
130436             slider   : me,
130437             index    : me.thumbs.length,
130438             constrain: me.constrainThumbs
130439         });
130440         me.thumbs.push(thumb);
130441
130442         //render the thumb now if needed
130443         if (me.rendered) {
130444             thumb.render();
130445         }
130446
130447         return thumb;
130448     },
130449
130450     /**
130451      * @private
130452      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
130453      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
130454      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
130455      * range, which can result in the user not being able to move any of them.
130456      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
130457      */
130458     promoteThumb: function(topThumb) {
130459         var thumbs = this.thumbs,
130460             ln = thumbs.length,
130461             zIndex, thumb, i;
130462
130463         for (i = 0; i < ln; i++) {
130464             thumb = thumbs[i];
130465
130466             if (thumb == topThumb) {
130467                 thumb.bringToFront();
130468             } else {
130469                 thumb.sendToBack();
130470             }
130471         }
130472     },
130473
130474     // private override
130475     onRender : function() {
130476         var me = this,
130477             i = 0,
130478             thumbs = me.thumbs,
130479             len = thumbs.length,
130480             thumb;
130481
130482         Ext.applyIf(me.subTplData, {
130483             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
130484             minValue: me.minValue,
130485             maxValue: me.maxValue,
130486             value: me.value
130487         });
130488
130489         me.addChildEls('endEl', 'innerEl', 'focusEl');
130490
130491         me.callParent(arguments);
130492
130493         //render each thumb
130494         for (; i < len; i++) {
130495             thumbs[i].render();
130496         }
130497
130498         //calculate the size of half a thumb
130499         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
130500         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
130501
130502     },
130503
130504     /**
130505      * Utility method to set the value of the field when the slider changes.
130506      * @param {Object} slider The slider object.
130507      * @param {Object} v The new value.
130508      * @private
130509      */
130510     onChange : function(slider, v) {
130511         this.setValue(v, undefined, true);
130512     },
130513
130514     /**
130515      * @private
130516      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
130517      */
130518     initEvents : function() {
130519         var me = this;
130520
130521         me.mon(me.el, {
130522             scope    : me,
130523             mousedown: me.onMouseDown,
130524             keydown  : me.onKeyDown,
130525             change : me.onChange
130526         });
130527
130528         me.focusEl.swallowEvent("click", true);
130529     },
130530
130531     /**
130532      * @private
130533      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
130534      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
130535      * @param {Ext.EventObject} e The click event
130536      */
130537     onMouseDown : function(e) {
130538         var me = this,
130539             thumbClicked = false,
130540             i = 0,
130541             thumbs = me.thumbs,
130542             len = thumbs.length,
130543             local;
130544
130545         if (me.disabled) {
130546             return;
130547         }
130548
130549         //see if the click was on any of the thumbs
130550         for (; i < len; i++) {
130551             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
130552         }
130553
130554         if (me.clickToChange && !thumbClicked) {
130555             local = me.innerEl.translatePoints(e.getXY());
130556             me.onClickChange(local);
130557         }
130558         me.focus();
130559     },
130560
130561     /**
130562      * @private
130563      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
130564      * Only changes the value if the click was within this.clickRange.
130565      * @param {Object} local Object containing top and left values for the click event.
130566      */
130567     onClickChange : function(local) {
130568         var me = this,
130569             thumb, index;
130570
130571         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
130572             //find the nearest thumb to the click event
130573             thumb = me.getNearest(local, 'left');
130574             if (!thumb.disabled) {
130575                 index = thumb.index;
130576                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
130577             }
130578         }
130579     },
130580
130581     /**
130582      * @private
130583      * Returns the nearest thumb to a click event, along with its distance
130584      * @param {Object} local Object containing top and left values from a click event
130585      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
130586      * @return {Object} The closest thumb object and its distance from the click event
130587      */
130588     getNearest: function(local, prop) {
130589         var me = this,
130590             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
130591             clickValue = me.reverseValue(localValue),
130592             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
130593             index = 0,
130594             nearest = null,
130595             thumbs = me.thumbs,
130596             i = 0,
130597             len = thumbs.length,
130598             thumb,
130599             value,
130600             dist;
130601
130602         for (; i < len; i++) {
130603             thumb = me.thumbs[i];
130604             value = thumb.value;
130605             dist  = Math.abs(value - clickValue);
130606
130607             if (Math.abs(dist <= nearestDistance)) {
130608                 nearest = thumb;
130609                 index = i;
130610                 nearestDistance = dist;
130611             }
130612         }
130613         return nearest;
130614     },
130615
130616     /**
130617      * @private
130618      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
130619      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
130620      * @param {Ext.EventObject} e The Event object
130621      */
130622     onKeyDown : function(e) {
130623         /*
130624          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
130625          * There's no real sane default for it, so leave it like this until we come up
130626          * with a better way of doing it.
130627          */
130628         var me = this,
130629             k,
130630             val;
130631
130632         if(me.disabled || me.thumbs.length !== 1) {
130633             e.preventDefault();
130634             return;
130635         }
130636         k = e.getKey();
130637
130638         switch(k) {
130639             case e.UP:
130640             case e.RIGHT:
130641                 e.stopEvent();
130642                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
130643                 me.setValue(0, val, undefined, true);
130644             break;
130645             case e.DOWN:
130646             case e.LEFT:
130647                 e.stopEvent();
130648                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
130649                 me.setValue(0, val, undefined, true);
130650             break;
130651             default:
130652                 e.preventDefault();
130653         }
130654     },
130655
130656     // private
130657     afterRender : function() {
130658         var me = this,
130659             i = 0,
130660             thumbs = me.thumbs,
130661             len = thumbs.length,
130662             thumb,
130663             v;
130664
130665         me.callParent(arguments);
130666
130667         for (; i < len; i++) {
130668             thumb = thumbs[i];
130669
130670             if (thumb.value !== undefined) {
130671                 v = me.normalizeValue(thumb.value);
130672                 if (v !== thumb.value) {
130673                     // delete this.value;
130674                     me.setValue(i, v, false);
130675                 } else {
130676                     thumb.move(me.translateValue(v), false);
130677                 }
130678             }
130679         }
130680     },
130681
130682     /**
130683      * @private
130684      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
130685      * the ratio is 2
130686      * @return {Number} The ratio of pixels to mapped values
130687      */
130688     getRatio : function() {
130689         var w = this.innerEl.getWidth(),
130690             v = this.maxValue - this.minValue;
130691         return v === 0 ? w : (w/v);
130692     },
130693
130694     /**
130695      * @private
130696      * Returns a snapped, constrained value when given a desired value
130697      * @param {Number} value Raw number value
130698      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
130699      */
130700     normalizeValue : function(v) {
130701         var me = this;
130702
130703         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
130704         v = Ext.util.Format.round(v, me.decimalPrecision);
130705         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
130706         return v;
130707     },
130708
130709     /**
130710      * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
130711      * value will be changed.
130712      * @param {Number} val The new minimum value
130713      */
130714     setMinValue : function(val) {
130715         var me = this,
130716             i = 0,
130717             thumbs = me.thumbs,
130718             len = thumbs.length,
130719             t;
130720
130721         me.minValue = val;
130722         if (me.rendered) {
130723             me.inputEl.dom.setAttribute('aria-valuemin', val);
130724         }
130725
130726         for (; i < len; ++i) {
130727             t = thumbs[i];
130728             t.value = t.value < val ? val : t.value;
130729         }
130730         me.syncThumbs();
130731     },
130732
130733     /**
130734      * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
130735      * value will be changed.
130736      * @param {Number} val The new maximum value
130737      */
130738     setMaxValue : function(val) {
130739         var me = this,
130740             i = 0,
130741             thumbs = me.thumbs,
130742             len = thumbs.length,
130743             t;
130744
130745         me.maxValue = val;
130746         if (me.rendered) {
130747             me.inputEl.dom.setAttribute('aria-valuemax', val);
130748         }
130749
130750         for (; i < len; ++i) {
130751             t = thumbs[i];
130752             t.value = t.value > val ? val : t.value;
130753         }
130754         me.syncThumbs();
130755     },
130756
130757     /**
130758      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
130759      * maxValue.
130760      * @param {Number} index Index of the thumb to move
130761      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
130762      * @param {Boolean} [animate=true] Turn on or off animation
130763      */
130764     setValue : function(index, value, animate, changeComplete) {
130765         var me = this,
130766             thumb = me.thumbs[index];
130767
130768         // ensures value is contstrained and snapped
130769         value = me.normalizeValue(value);
130770
130771         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
130772             thumb.value = value;
130773             if (me.rendered) {
130774                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
130775                 // Perhaps this should go on each thumb element rather than the outer element.
130776                 me.inputEl.set({
130777                     'aria-valuenow': value,
130778                     'aria-valuetext': value
130779                 });
130780
130781                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
130782
130783                 me.fireEvent('change', me, value, thumb);
130784                 if (changeComplete) {
130785                     me.fireEvent('changecomplete', me, value, thumb);
130786                 }
130787             }
130788         }
130789     },
130790
130791     /**
130792      * @private
130793      */
130794     translateValue : function(v) {
130795         var ratio = this.getRatio();
130796         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
130797     },
130798
130799     /**
130800      * @private
130801      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
130802      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
130803      * returns 200
130804      * @param {Number} pos The position along the slider to return a mapped value for
130805      * @return {Number} The mapped value for the given position
130806      */
130807     reverseValue : function(pos) {
130808         var ratio = this.getRatio();
130809         return (pos + (this.minValue * ratio)) / ratio;
130810     },
130811
130812     // private
130813     focus : function() {
130814         this.focusEl.focus(10);
130815     },
130816
130817     //private
130818     onDisable: function() {
130819         var me = this,
130820             i = 0,
130821             thumbs = me.thumbs,
130822             len = thumbs.length,
130823             thumb,
130824             el,
130825             xy;
130826
130827         me.callParent();
130828
130829         for (; i < len; i++) {
130830             thumb = thumbs[i];
130831             el = thumb.el;
130832
130833             thumb.disable();
130834
130835             if(Ext.isIE) {
130836                 //IE breaks when using overflow visible and opacity other than 1.
130837                 //Create a place holder for the thumb and display it.
130838                 xy = el.getXY();
130839                 el.hide();
130840
130841                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
130842
130843                 if (!me.thumbHolder) {
130844                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
130845                 }
130846
130847                 me.thumbHolder.show().setXY(xy);
130848             }
130849         }
130850     },
130851
130852     //private
130853     onEnable: function() {
130854         var me = this,
130855             i = 0,
130856             thumbs = me.thumbs,
130857             len = thumbs.length,
130858             thumb,
130859             el;
130860
130861         this.callParent();
130862
130863         for (; i < len; i++) {
130864             thumb = thumbs[i];
130865             el = thumb.el;
130866
130867             thumb.enable();
130868
130869             if (Ext.isIE) {
130870                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
130871
130872                 if (me.thumbHolder) {
130873                     me.thumbHolder.hide();
130874                 }
130875
130876                 el.show();
130877                 me.syncThumbs();
130878             }
130879         }
130880     },
130881
130882     /**
130883      * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
130884      * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
130885      * auto width, this method can be called from another resize handler to sync the Slider if necessary.
130886      */
130887     syncThumbs : function() {
130888         if (this.rendered) {
130889             var thumbs = this.thumbs,
130890                 length = thumbs.length,
130891                 i = 0;
130892
130893             for (; i < length; i++) {
130894                 thumbs[i].move(this.translateValue(thumbs[i].value));
130895             }
130896         }
130897     },
130898
130899     /**
130900      * Returns the current value of the slider
130901      * @param {Number} index The index of the thumb to return a value for
130902      * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
130903      * no index is given.
130904      */
130905     getValue : function(index) {
130906         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
130907     },
130908
130909     /**
130910      * Returns an array of values - one for the location of each thumb
130911      * @return {Number[]} The set of thumb values
130912      */
130913     getValues: function() {
130914         var values = [],
130915             i = 0,
130916             thumbs = this.thumbs,
130917             len = thumbs.length;
130918
130919         for (; i < len; i++) {
130920             values.push(thumbs[i].value);
130921         }
130922
130923         return values;
130924     },
130925
130926     getSubmitValue: function() {
130927         var me = this;
130928         return (me.disabled || !me.submitValue) ? null : me.getValue();
130929     },
130930
130931     reset: function() {
130932         var me = this,
130933             Array = Ext.Array;
130934         Array.forEach(Array.from(me.originalValue), function(val, i) {
130935             me.setValue(i, val);
130936         });
130937         me.clearInvalid();
130938         // delete here so we reset back to the original state
130939         delete me.wasValid;
130940     },
130941
130942     // private
130943     beforeDestroy : function() {
130944         var me = this;
130945
130946         Ext.destroy(me.innerEl, me.endEl, me.focusEl);
130947         Ext.each(me.thumbs, function(thumb) {
130948             Ext.destroy(thumb);
130949         }, me);
130950
130951         me.callParent();
130952     },
130953
130954     statics: {
130955         // Method overrides to support slider with vertical orientation
130956         Vertical: {
130957             getRatio : function() {
130958                 var h = this.innerEl.getHeight(),
130959                     v = this.maxValue - this.minValue;
130960                 return h/v;
130961             },
130962
130963             onClickChange : function(local) {
130964                 var me = this,
130965                     thumb, index, bottom;
130966
130967                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
130968                     thumb = me.getNearest(local, 'top');
130969                     if (!thumb.disabled) {
130970                         index = thumb.index;
130971                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
130972
130973                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
130974                     }
130975                 }
130976             }
130977         }
130978     }
130979 });
130980
130981 /**
130982  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
130983  * and animation. Can be added as an item to any container.
130984  *
130985  *     @example
130986  *     Ext.create('Ext.slider.Single', {
130987  *         width: 200,
130988  *         value: 50,
130989  *         increment: 10,
130990  *         minValue: 0,
130991  *         maxValue: 100,
130992  *         renderTo: Ext.getBody()
130993  *     });
130994  *
130995  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
130996  */
130997 Ext.define('Ext.slider.Single', {
130998     extend: 'Ext.slider.Multi',
130999     alias: ['widget.slider', 'widget.sliderfield'],
131000     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
131001
131002     /**
131003      * Returns the current value of the slider
131004      * @return {Number} The current value of the slider
131005      */
131006     getValue: function() {
131007         // just returns the value of the first thumb, which should be the only one in a single slider
131008         return this.callParent([0]);
131009     },
131010
131011     /**
131012      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
131013      * maxValue.
131014      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
131015      * @param {Boolean} [animate] Turn on or off animation
131016      */
131017     setValue: function(value, animate) {
131018         var args = Ext.toArray(arguments),
131019             len  = args.length;
131020
131021         // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
131022         // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
131023         // signature without the required index. The index will always be 0 for a single slider
131024         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
131025             args.unshift(0);
131026         }
131027
131028         return this.callParent(args);
131029     },
131030
131031     // private
131032     getNearest : function(){
131033         // Since there's only 1 thumb, it's always the nearest
131034         return this.thumbs[0];
131035     }
131036 });
131037
131038 /**
131039  * @author Ed Spencer
131040  * @class Ext.tab.Tab
131041  * @extends Ext.button.Button
131042  *
131043  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
131044  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
131045  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
131046  */
131047 Ext.define('Ext.tab.Tab', {
131048     extend: 'Ext.button.Button',
131049     alias: 'widget.tab',
131050
131051     requires: [
131052         'Ext.layout.component.Tab',
131053         'Ext.util.KeyNav'
131054     ],
131055
131056     componentLayout: 'tab',
131057
131058     isTab: true,
131059
131060     baseCls: Ext.baseCSSPrefix + 'tab',
131061
131062     /**
131063      * @cfg {String} activeCls
131064      * The CSS class to be applied to a Tab when it is active.
131065      * Providing your own CSS for this class enables you to customize the active state.
131066      */
131067     activeCls: 'active',
131068
131069     /**
131070      * @cfg {String} disabledCls
131071      * The CSS class to be applied to a Tab when it is disabled.
131072      */
131073
131074     /**
131075      * @cfg {String} closableCls
131076      * The CSS class which is added to the tab when it is closable
131077      */
131078     closableCls: 'closable',
131079
131080     /**
131081      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
131082      */
131083     closable: true,
131084
131085     /**
131086      * @cfg {String} closeText
131087      * The accessible text label for the close button link; only used when {@link #closable} = true.
131088      */
131089     closeText: 'Close Tab',
131090
131091     /**
131092      * @property {Boolean} active
131093      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
131094      */
131095     active: false,
131096
131097     /**
131098      * @property closable
131099      * @type Boolean
131100      * True if the tab is currently closable
131101      */
131102
131103     scale: false,
131104
131105     position: 'top',
131106
131107     initComponent: function() {
131108         var me = this;
131109
131110         me.addEvents(
131111             /**
131112              * @event activate
131113              * Fired when the tab is activated.
131114              * @param {Ext.tab.Tab} this
131115              */
131116             'activate',
131117
131118             /**
131119              * @event deactivate
131120              * Fired when the tab is deactivated.
131121              * @param {Ext.tab.Tab} this
131122              */
131123             'deactivate',
131124
131125             /**
131126              * @event beforeclose
131127              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
131128              * false from any listener to stop the close event being fired
131129              * @param {Ext.tab.Tab} tab The Tab object
131130              */
131131             'beforeclose',
131132
131133             /**
131134              * @event close
131135              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
131136              * @param {Ext.tab.Tab} tab The Tab object
131137              */
131138             'close'
131139         );
131140
131141         me.callParent(arguments);
131142
131143         if (me.card) {
131144             me.setCard(me.card);
131145         }
131146     },
131147
131148     /**
131149      * @ignore
131150      */
131151     onRender: function() {
131152         var me = this,
131153             tabBar = me.up('tabbar'),
131154             tabPanel = me.up('tabpanel');
131155
131156         me.addClsWithUI(me.position);
131157
131158         // Set all the state classNames, as they need to include the UI
131159         // me.disabledCls = me.getClsWithUIs('disabled');
131160
131161         me.syncClosableUI();
131162
131163         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
131164         if (!me.minWidth) {
131165             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
131166             if (!me.minWidth && tabPanel) {
131167                 me.minWidth = tabPanel.minTabWidth;
131168             }
131169             if (me.minWidth && me.iconCls) {
131170                 me.minWidth += 25;
131171             }
131172         }
131173         if (!me.maxWidth) {
131174             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
131175             if (!me.maxWidth && tabPanel) {
131176                 me.maxWidth = tabPanel.maxTabWidth;
131177             }
131178         }
131179
131180         me.callParent(arguments);
131181
131182         if (me.active) {
131183             me.activate(true);
131184         }
131185
131186         me.syncClosableElements();
131187
131188         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
131189             enter: me.onEnterKey,
131190             del: me.onDeleteKey,
131191             scope: me
131192         });
131193     },
131194
131195     // inherit docs
131196     enable : function(silent) {
131197         var me = this;
131198
131199         me.callParent(arguments);
131200
131201         me.removeClsWithUI(me.position + '-disabled');
131202
131203         return me;
131204     },
131205
131206     // inherit docs
131207     disable : function(silent) {
131208         var me = this;
131209
131210         me.callParent(arguments);
131211
131212         me.addClsWithUI(me.position + '-disabled');
131213
131214         return me;
131215     },
131216
131217     /**
131218      * @ignore
131219      */
131220     onDestroy: function() {
131221         var me = this;
131222
131223         if (me.closeEl) {
131224             me.closeEl.un('click', Ext.EventManager.preventDefault);
131225             me.closeEl = null;
131226         }
131227
131228         Ext.destroy(me.keyNav);
131229         delete me.keyNav;
131230
131231         me.callParent(arguments);
131232     },
131233
131234     /**
131235      * Sets the tab as either closable or not
131236      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
131237      * close button will appear on the tab)
131238      */
131239     setClosable: function(closable) {
131240         var me = this;
131241
131242         // Closable must be true if no args
131243         closable = (!arguments.length || !!closable);
131244
131245         if (me.closable != closable) {
131246             me.closable = closable;
131247
131248             // set property on the user-facing item ('card'):
131249             if (me.card) {
131250                 me.card.closable = closable;
131251             }
131252
131253             me.syncClosableUI();
131254
131255             if (me.rendered) {
131256                 me.syncClosableElements();
131257
131258                 // Tab will change width to accommodate close icon
131259                 me.doComponentLayout();
131260                 if (me.ownerCt) {
131261                     me.ownerCt.doLayout();
131262                 }
131263             }
131264         }
131265     },
131266
131267     /**
131268      * This method ensures that the closeBtn element exists or not based on 'closable'.
131269      * @private
131270      */
131271     syncClosableElements: function () {
131272         var me = this;
131273
131274         if (me.closable) {
131275             if (!me.closeEl) {
131276                 me.closeEl = me.el.createChild({
131277                     tag: 'a',
131278                     cls: me.baseCls + '-close-btn',
131279                     href: '#',
131280                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
131281                     title: me.closeText
131282                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
131283             }
131284         } else {
131285             var closeEl = me.closeEl;
131286             if (closeEl) {
131287                 closeEl.un('click', Ext.EventManager.preventDefault);
131288                 closeEl.remove();
131289                 me.closeEl = null;
131290             }
131291         }
131292     },
131293
131294     /**
131295      * This method ensures that the UI classes are added or removed based on 'closable'.
131296      * @private
131297      */
131298     syncClosableUI: function () {
131299         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
131300
131301         if (me.closable) {
131302             me.addClsWithUI(classes);
131303         } else {
131304             me.removeClsWithUI(classes);
131305         }
131306     },
131307
131308     /**
131309      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
131310      * belongs to and would not need to be done by the developer
131311      * @param {Ext.Component} card The card to set
131312      */
131313     setCard: function(card) {
131314         var me = this;
131315
131316         me.card = card;
131317         me.setText(me.title || card.title);
131318         me.setIconCls(me.iconCls || card.iconCls);
131319     },
131320
131321     /**
131322      * @private
131323      * Listener attached to click events on the Tab's close button
131324      */
131325     onCloseClick: function() {
131326         var me = this;
131327
131328         if (me.fireEvent('beforeclose', me) !== false) {
131329             if (me.tabBar) {
131330                 if (me.tabBar.closeTab(me) === false) {
131331                     // beforeclose on the panel vetoed the event, stop here
131332                     return;
131333                 }
131334             } else {
131335                 // if there's no tabbar, fire the close event
131336                 me.fireEvent('close', me);
131337             }
131338         }
131339     },
131340
131341     /**
131342      * Fires the close event on the tab.
131343      * @private
131344      */
131345     fireClose: function(){
131346         this.fireEvent('close', this);
131347     },
131348
131349     /**
131350      * @private
131351      */
131352     onEnterKey: function(e) {
131353         var me = this;
131354
131355         if (me.tabBar) {
131356             me.tabBar.onClick(e, me.el);
131357         }
131358     },
131359
131360    /**
131361      * @private
131362      */
131363     onDeleteKey: function(e) {
131364         var me = this;
131365
131366         if (me.closable) {
131367             me.onCloseClick();
131368         }
131369     },
131370
131371     // @private
131372     activate : function(supressEvent) {
131373         var me = this;
131374
131375         me.active = true;
131376         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
131377
131378         if (supressEvent !== true) {
131379             me.fireEvent('activate', me);
131380         }
131381     },
131382
131383     // @private
131384     deactivate : function(supressEvent) {
131385         var me = this;
131386
131387         me.active = false;
131388         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
131389
131390         if (supressEvent !== true) {
131391             me.fireEvent('deactivate', me);
131392         }
131393     }
131394 });
131395
131396 /**
131397  * @author Ed Spencer
131398  * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
131399  * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
131400  */
131401 Ext.define('Ext.tab.Bar', {
131402     extend: 'Ext.panel.Header',
131403     alias: 'widget.tabbar',
131404     baseCls: Ext.baseCSSPrefix + 'tab-bar',
131405
131406     requires: [
131407         'Ext.tab.Tab',
131408         'Ext.FocusManager'
131409     ],
131410
131411     isTabBar: true,
131412     
131413     /**
131414      * @cfg {String} title @hide
131415      */
131416     
131417     /**
131418      * @cfg {String} iconCls @hide
131419      */
131420
131421     // @private
131422     defaultType: 'tab',
131423
131424     /**
131425      * @cfg {Boolean} plain
131426      * True to not show the full background on the tabbar
131427      */
131428     plain: false,
131429
131430     // @private
131431     renderTpl: [
131432         '<div id="{id}-body" class="{baseCls}-body <tpl if="bodyCls"> {bodyCls}</tpl> <tpl if="ui"> {baseCls}-body-{ui}<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>',
131433         '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
131434     ],
131435
131436     /**
131437      * @cfg {Number} minTabWidth
131438      * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
131439      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
131440      */
131441
131442     /**
131443      * @cfg {Number} maxTabWidth
131444      * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
131445      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
131446      */
131447
131448     // @private
131449     initComponent: function() {
131450         var me = this,
131451             keys;
131452
131453         if (me.plain) {
131454             me.setUI(me.ui + '-plain');
131455         }
131456
131457         me.addClsWithUI(me.dock);
131458
131459         me.addEvents(
131460             /**
131461              * @event change
131462              * Fired when the currently-active tab has changed
131463              * @param {Ext.tab.Bar} tabBar The TabBar
131464              * @param {Ext.tab.Tab} tab The new Tab
131465              * @param {Ext.Component} card The card that was just shown in the TabPanel
131466              */
131467             'change'
131468         );
131469
131470         me.addChildEls('body', 'strip');
131471         me.callParent(arguments);
131472
131473         // TabBar must override the Header's align setting.
131474         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
131475         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
131476
131477         me.remove(me.titleCmp);
131478         delete me.titleCmp;
131479
131480         // Subscribe to Ext.FocusManager for key navigation
131481         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
131482         Ext.FocusManager.subscribe(me, {
131483             keys: keys
131484         });
131485
131486         Ext.apply(me.renderData, {
131487             bodyCls: me.bodyCls
131488         });
131489     },
131490
131491     // @private
131492     onAdd: function(tab) {
131493         tab.position = this.dock;
131494         this.callParent(arguments);
131495     },
131496     
131497     onRemove: function(tab) {
131498         var me = this;
131499         
131500         if (tab === me.previousTab) {
131501             me.previousTab = null;
131502         }
131503         if (me.items.getCount() === 0) {
131504             me.activeTab = null;
131505         }
131506         me.callParent(arguments);    
131507     },
131508
131509     // @private
131510     afterRender: function() {
131511         var me = this;
131512
131513         me.mon(me.el, {
131514             scope: me,
131515             click: me.onClick,
131516             delegate: '.' + Ext.baseCSSPrefix + 'tab'
131517         });
131518         me.callParent(arguments);
131519
131520     },
131521
131522     afterComponentLayout : function() {
131523         var me = this;
131524
131525         me.callParent(arguments);
131526         me.strip.setWidth(me.el.getWidth());
131527     },
131528
131529     // @private
131530     onClick: function(e, target) {
131531         // The target might not be a valid tab el.
131532         var tab = Ext.getCmp(target.id),
131533             tabPanel = this.tabPanel;
131534
131535         target = e.getTarget();
131536
131537         if (tab && tab.isDisabled && !tab.isDisabled()) {
131538             if (tab.closable && target === tab.closeEl.dom) {
131539                 tab.onCloseClick();
131540             } else {
131541                 if (tabPanel) {
131542                     // TabPanel will card setActiveTab of the TabBar
131543                     tabPanel.setActiveTab(tab.card);
131544                 } else {
131545                     this.setActiveTab(tab);
131546                 }
131547                 tab.focus();
131548             }
131549         }
131550     },
131551
131552     /**
131553      * @private
131554      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
131555      * @param {Ext.tab.Tab} tab The tab to close
131556      */
131557     closeTab: function(tab) {
131558         var me = this,
131559             card = tab.card,
131560             tabPanel = me.tabPanel,
131561             nextTab;
131562
131563         if (card && card.fireEvent('beforeclose', card) === false) {
131564             return false;
131565         }
131566
131567         if (tab.active && me.items.getCount() > 1) {
131568             nextTab = me.previousTab || tab.next('tab') || me.items.first();
131569             me.setActiveTab(nextTab);
131570             if (tabPanel) {
131571                 tabPanel.setActiveTab(nextTab.card);
131572             }
131573         }
131574         /*
131575          * force the close event to fire. By the time this function returns,
131576          * the tab is already destroyed and all listeners have been purged
131577          * so the tab can't fire itself.
131578          */
131579         tab.fireClose();
131580         me.remove(tab);
131581
131582         if (tabPanel && card) {
131583             card.fireEvent('close', card);
131584             tabPanel.remove(card);
131585         }
131586
131587         if (nextTab) {
131588             nextTab.focus();
131589         }
131590     },
131591
131592     /**
131593      * @private
131594      * Marks the given tab as active
131595      * @param {Ext.tab.Tab} tab The tab to mark active
131596      */
131597     setActiveTab: function(tab) {
131598         if (tab.disabled) {
131599             return;
131600         }
131601         var me = this;
131602         if (me.activeTab) {
131603             me.previousTab = me.activeTab;
131604             me.activeTab.deactivate();
131605         }
131606         tab.activate();
131607
131608         if (me.rendered) {
131609             me.layout.layout();
131610             tab.el && tab.el.scrollIntoView(me.layout.getRenderTarget());
131611         }
131612         me.activeTab = tab;
131613         me.fireEvent('change', me, tab, tab.card);
131614     }
131615 });
131616
131617 /**
131618  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
131619  *
131620  * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
131621  * layout purposes, but also have special support for containing child Components
131622  * (`{@link Ext.container.Container#items items}`) that are managed using a
131623  * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
131624  *
131625  * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
131626  * This makes the child tab Component, and all its descendants **unusable**.  To enable re-use of a tab,
131627  * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
131628  *
131629  * ## TabPanel's layout
131630  *
131631  * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
131632  * Panels added to the TabPanel will have their header hidden by default because the Tab will
131633  * automatically take the Panel's configured title and icon.
131634  *
131635  * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
131636  * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
131637  * This means that a TabPanel will not display any configured title, and will not display any configured
131638  * header {@link Ext.panel.Panel#tools tools}.
131639  *
131640  * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
131641  * `{@link Ext.container.Container#layout layout: 'fit'}`.
131642  *
131643  * ## Controlling tabs
131644  *
131645  * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
131646  * by specifying the tabConfig option:
131647  *
131648  *     @example
131649  *     Ext.create('Ext.tab.Panel', {
131650  *         width: 400,
131651  *         height: 400,
131652  *         renderTo: document.body,
131653  *         items: [{
131654  *             title: 'Foo'
131655  *         }, {
131656  *             title: 'Bar',
131657  *             tabConfig: {
131658  *                 title: 'Custom Title',
131659  *                 tooltip: 'A button tooltip'
131660  *             }
131661  *         }]
131662  *     });
131663  *
131664  * # Examples
131665  *
131666  * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
131667  * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
131668  * active by default.
131669  *
131670  *     @example
131671  *     Ext.create('Ext.tab.Panel', {
131672  *         width: 300,
131673  *         height: 200,
131674  *         activeTab: 0,
131675  *         items: [
131676  *             {
131677  *                 title: 'Tab 1',
131678  *                 bodyPadding: 10,
131679  *                 html : 'A simple tab'
131680  *             },
131681  *             {
131682  *                 title: 'Tab 2',
131683  *                 html : 'Another one'
131684  *             }
131685  *         ],
131686  *         renderTo : Ext.getBody()
131687  *     });
131688  *
131689  * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
131690  * tab button hidden initially. Items can be subsequently hidden and show by accessing the
131691  * tab property on the child item.
131692  *
131693  *     @example
131694  *     var tabs = Ext.create('Ext.tab.Panel', {
131695  *         width: 400,
131696  *         height: 400,
131697  *         renderTo: document.body,
131698  *         items: [{
131699  *             title: 'Home',
131700  *             html: 'Home',
131701  *             itemId: 'home'
131702  *         }, {
131703  *             title: 'Users',
131704  *             html: 'Users',
131705  *             itemId: 'users',
131706  *             hidden: true
131707  *         }, {
131708  *             title: 'Tickets',
131709  *             html: 'Tickets',
131710  *             itemId: 'tickets'
131711  *         }]
131712  *     });
131713  *
131714  *     setTimeout(function(){
131715  *         tabs.child('#home').tab.hide();
131716  *         var users = tabs.child('#users');
131717  *         users.tab.show();
131718  *         tabs.setActiveTab(users);
131719  *     }, 1000);
131720  *
131721  * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
131722  *
131723  *     @example
131724  *     Ext.create('Ext.tab.Panel', {
131725  *         width: 300,
131726  *         height: 200,
131727  *         activeTab: 0,
131728  *         plain: true,
131729  *         items: [
131730  *             {
131731  *                 title: 'Tab 1',
131732  *                 bodyPadding: 10,
131733  *                 html : 'A simple tab'
131734  *             },
131735  *             {
131736  *                 title: 'Tab 2',
131737  *                 html : 'Another one'
131738  *             }
131739  *         ],
131740  *         renderTo : Ext.getBody()
131741  *     });
131742  *
131743  * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
131744  * position where the tabs are displayed. The available options for this are `'top'` (default) and
131745  * `'bottom'`.
131746  *
131747  *     @example
131748  *     Ext.create('Ext.tab.Panel', {
131749  *         width: 300,
131750  *         height: 200,
131751  *         activeTab: 0,
131752  *         bodyPadding: 10,
131753  *         tabPosition: 'bottom',
131754  *         items: [
131755  *             {
131756  *                 title: 'Tab 1',
131757  *                 html : 'A simple tab'
131758  *             },
131759  *             {
131760  *                 title: 'Tab 2',
131761  *                 html : 'Another one'
131762  *             }
131763  *         ],
131764  *         renderTo : Ext.getBody()
131765  *     });
131766  *
131767  * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
131768  * current active tab. You can either give it an index or an instance of a tab. For example:
131769  *
131770  *     @example
131771  *     var tabs = Ext.create('Ext.tab.Panel', {
131772  *         items: [
131773  *             {
131774  *                 id   : 'my-tab',
131775  *                 title: 'Tab 1',
131776  *                 html : 'A simple tab'
131777  *             },
131778  *             {
131779  *                 title: 'Tab 2',
131780  *                 html : 'Another one'
131781  *             }
131782  *         ],
131783  *         renderTo : Ext.getBody()
131784  *     });
131785  *
131786  *     var tab = Ext.getCmp('my-tab');
131787  *
131788  *     Ext.create('Ext.button.Button', {
131789  *         renderTo: Ext.getBody(),
131790  *         text    : 'Select the first tab',
131791  *         scope   : this,
131792  *         handler : function() {
131793  *             tabs.setActiveTab(tab);
131794  *         }
131795  *     });
131796  *
131797  *     Ext.create('Ext.button.Button', {
131798  *         text    : 'Select the second tab',
131799  *         scope   : this,
131800  *         handler : function() {
131801  *             tabs.setActiveTab(1);
131802  *         },
131803  *         renderTo : Ext.getBody()
131804  *     });
131805  *
131806  * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
131807  *
131808  *     @example
131809  *     var tabs = Ext.create('Ext.tab.Panel', {
131810  *         items: [
131811  *             {
131812  *                 title: 'Tab 1',
131813  *                 html : 'A simple tab'
131814  *             },
131815  *             {
131816  *                 title: 'Tab 2',
131817  *                 html : 'Another one'
131818  *             }
131819  *         ],
131820  *         renderTo : Ext.getBody()
131821  *     });
131822  *
131823  *     Ext.create('Ext.button.Button', {
131824  *         text    : 'Get active tab',
131825  *         scope   : this,
131826  *         handler : function() {
131827  *             var tab = tabs.getActiveTab();
131828  *             alert('Current tab: ' + tab.title);
131829  *         },
131830  *         renderTo : Ext.getBody()
131831  *     });
131832  *
131833  * Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config
131834  * object for a panel.
131835  *
131836  *     @example
131837  *     var tabs = Ext.create('Ext.tab.Panel', {
131838  *         items: [
131839  *             {
131840  *                 title: 'Tab 1',
131841  *                 html : 'A simple tab'
131842  *             },
131843  *             {
131844  *                 title: 'Tab 2',
131845  *                 html : 'Another one'
131846  *             }
131847  *         ],
131848  *         renderTo : Ext.getBody()
131849  *     });
131850  *
131851  *     Ext.create('Ext.button.Button', {
131852  *         text    : 'New tab',
131853  *         scope   : this,
131854  *         handler : function() {
131855  *             var tab = tabs.add({
131856  *                 // we use the tabs.items property to get the length of current items/tabs
131857  *                 title: 'Tab ' + (tabs.items.length + 1),
131858  *                 html : 'Another one'
131859  *             });
131860  *
131861  *             tabs.setActiveTab(tab);
131862  *         },
131863  *         renderTo : Ext.getBody()
131864  *     });
131865  *
131866  * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method
131867  * with an config object for a panel.
131868  *
131869  *     @example
131870  *     var tabs = Ext.create('Ext.tab.Panel', {
131871  *         items: [
131872  *             {
131873  *                 title: 'Tab 1',
131874  *                 html : 'A simple tab'
131875  *             },
131876  *             {
131877  *                 id   : 'remove-this-tab',
131878  *                 title: 'Tab 2',
131879  *                 html : 'Another one'
131880  *             }
131881  *         ],
131882  *         renderTo : Ext.getBody()
131883  *     });
131884  *
131885  *     Ext.create('Ext.button.Button', {
131886  *         text    : 'Remove tab',
131887  *         scope   : this,
131888  *         handler : function() {
131889  *             var tab = Ext.getCmp('remove-this-tab');
131890  *             tabs.remove(tab);
131891  *         },
131892  *         renderTo : Ext.getBody()
131893  *     });
131894  */
131895 Ext.define('Ext.tab.Panel', {
131896     extend: 'Ext.panel.Panel',
131897     alias: 'widget.tabpanel',
131898     alternateClassName: ['Ext.TabPanel'],
131899
131900     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
131901
131902     /**
131903      * @cfg {String} tabPosition
131904      * The position where the tab strip should be rendered. Can be `top` or `bottom`.
131905      */
131906     tabPosition : 'top',
131907
131908     /**
131909      * @cfg {String/Number} activeItem
131910      * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
131911      */
131912
131913     /**
131914      * @cfg {String/Number/Ext.Component} activeTab
131915      * The tab to activate initially. Either an ID, index or the tab component itself.
131916      */
131917
131918     /**
131919      * @cfg {Object} tabBar
131920      * Optional configuration object for the internal {@link Ext.tab.Bar}.
131921      * If present, this is passed straight through to the TabBar's constructor
131922      */
131923
131924     /**
131925      * @cfg {Object} layout
131926      * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
131927      * If present, this is passed straight through to the layout's constructor
131928      */
131929
131930     /**
131931      * @cfg {Boolean} removePanelHeader
131932      * True to instruct each Panel added to the TabContainer to not render its header element.
131933      * This is to ensure that the title of the panel does not appear twice.
131934      */
131935     removePanelHeader: true,
131936
131937     /**
131938      * @cfg {Boolean} plain
131939      * True to not show the full background on the TabBar.
131940      */
131941     plain: false,
131942
131943     /**
131944      * @cfg {String} itemCls
131945      * The class added to each child item of this TabPanel.
131946      */
131947     itemCls: 'x-tabpanel-child',
131948
131949     /**
131950      * @cfg {Number} minTabWidth
131951      * The minimum width for a tab in the {@link #tabBar}.
131952      */
131953     minTabWidth: undefined,
131954
131955     /**
131956      * @cfg {Number} maxTabWidth The maximum width for each tab.
131957      */
131958     maxTabWidth: undefined,
131959
131960     /**
131961      * @cfg {Boolean} deferredRender
131962      *
131963      * True by default to defer the rendering of child {@link Ext.container.Container#items items} to the browsers DOM
131964      * until a tab is activated. False will render all contained {@link Ext.container.Container#items items} as soon as
131965      * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
131966      * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
131967      * performance.
131968      *
131969      * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
131970      * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
131971      *
131972      * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
131973      */
131974     deferredRender : true,
131975
131976     //inherit docs
131977     initComponent: function() {
131978         var me = this,
131979             dockedItems = me.dockedItems || [],
131980             activeTab = me.activeTab || 0;
131981
131982         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
131983             owner: me,
131984             deferredRender: me.deferredRender,
131985             itemCls: me.itemCls
131986         }, me.layout));
131987
131988         /**
131989          * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
131990          */
131991         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
131992             dock: me.tabPosition,
131993             plain: me.plain,
131994             border: me.border,
131995             cardLayout: me.layout,
131996             tabPanel: me
131997         }));
131998
131999         if (dockedItems && !Ext.isArray(dockedItems)) {
132000             dockedItems = [dockedItems];
132001         }
132002
132003         dockedItems.push(me.tabBar);
132004         me.dockedItems = dockedItems;
132005
132006         me.addEvents(
132007             /**
132008              * @event
132009              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
132010              * the tabchange
132011              * @param {Ext.tab.Panel} tabPanel The TabPanel
132012              * @param {Ext.Component} newCard The card that is about to be activated
132013              * @param {Ext.Component} oldCard The card that is currently active
132014              */
132015             'beforetabchange',
132016
132017             /**
132018              * @event
132019              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
132020              * @param {Ext.tab.Panel} tabPanel The TabPanel
132021              * @param {Ext.Component} newCard The newly activated item
132022              * @param {Ext.Component} oldCard The previously active item
132023              */
132024             'tabchange'
132025         );
132026         me.callParent(arguments);
132027
132028         //set the active tab
132029         me.setActiveTab(activeTab);
132030         //set the active tab after initial layout
132031         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
132032     },
132033
132034     /**
132035      * @private
132036      * We have to wait until after the initial layout to visually activate the activeTab (if set).
132037      * The active tab has different margins than normal tabs, so if the initial layout happens with
132038      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
132039      * until after the initial layout avoids this issue.
132040      */
132041     afterInitialLayout: function() {
132042         var me = this,
132043             card = me.getComponent(me.activeTab);
132044
132045         if (card) {
132046             me.layout.setActiveItem(card);
132047         }
132048     },
132049
132050     /**
132051      * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
132052      * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
132053      */
132054     setActiveTab: function(card) {
132055         var me = this,
132056             previous;
132057
132058         card = me.getComponent(card);
132059         if (card) {
132060             previous = me.getActiveTab();
132061
132062             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
132063                 return false;
132064             }
132065
132066             me.tabBar.setActiveTab(card.tab);
132067             me.activeTab = card;
132068             if (me.rendered) {
132069                 me.layout.setActiveItem(card);
132070             }
132071
132072             if (previous && previous !== card) {
132073                 me.fireEvent('tabchange', me, card, previous);
132074             }
132075         }
132076     },
132077
132078     /**
132079      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
132080      * child component this will return whatever was configured in the {@link #activeTab} config option
132081      * @return {String/Number/Ext.Component} The currently active item
132082      */
132083     getActiveTab: function() {
132084         return this.activeTab;
132085     },
132086
132087     /**
132088      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
132089      * @return {Ext.tab.Bar} The TabBar
132090      */
132091     getTabBar: function() {
132092         return this.tabBar;
132093     },
132094
132095     /**
132096      * @ignore
132097      * Makes sure we have a Tab for each item added to the TabPanel
132098      */
132099     onAdd: function(item, index) {
132100         var me = this,
132101             cfg = item.tabConfig || {},
132102             defaultConfig = {
132103                 xtype: 'tab',
132104                 card: item,
132105                 disabled: item.disabled,
132106                 closable: item.closable,
132107                 hidden: item.hidden,
132108                 tabBar: me.tabBar
132109             };
132110
132111         if (item.closeText) {
132112             defaultConfig.closeText = item.closeText;
132113         }
132114         cfg = Ext.applyIf(cfg, defaultConfig);
132115         item.tab = me.tabBar.insert(index, cfg);
132116
132117         item.on({
132118             scope : me,
132119             enable: me.onItemEnable,
132120             disable: me.onItemDisable,
132121             beforeshow: me.onItemBeforeShow,
132122             iconchange: me.onItemIconChange,
132123             titlechange: me.onItemTitleChange
132124         });
132125
132126         if (item.isPanel) {
132127             if (me.removePanelHeader) {
132128                 item.preventHeader = true;
132129                 if (item.rendered) {
132130                     item.updateHeader();
132131                 }
132132             }
132133             if (item.isPanel && me.border) {
132134                 item.setBorder(false);
132135             }
132136         }
132137
132138         // ensure that there is at least one active tab
132139         if (this.rendered && me.items.getCount() === 1) {
132140             me.setActiveTab(0);
132141         }
132142     },
132143
132144     /**
132145      * @private
132146      * Enable corresponding tab when item is enabled.
132147      */
132148     onItemEnable: function(item){
132149         item.tab.enable();
132150     },
132151
132152     /**
132153      * @private
132154      * Disable corresponding tab when item is enabled.
132155      */
132156     onItemDisable: function(item){
132157         item.tab.disable();
132158     },
132159
132160     /**
132161      * @private
132162      * Sets activeTab before item is shown.
132163      */
132164     onItemBeforeShow: function(item) {
132165         if (item !== this.activeTab) {
132166             this.setActiveTab(item);
132167             return false;
132168         }
132169     },
132170
132171     /**
132172      * @private
132173      * Update the tab iconCls when panel iconCls has been set or changed.
132174      */
132175     onItemIconChange: function(item, newIconCls) {
132176         item.tab.setIconCls(newIconCls);
132177         this.getTabBar().doLayout();
132178     },
132179
132180     /**
132181      * @private
132182      * Update the tab title when panel title has been set or changed.
132183      */
132184     onItemTitleChange: function(item, newTitle) {
132185         item.tab.setText(newTitle);
132186         this.getTabBar().doLayout();
132187     },
132188
132189
132190     /**
132191      * @ignore
132192      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
132193      * so we can do preprocessing before then to find the card's index
132194      */
132195     doRemove: function(item, autoDestroy) {
132196         var me = this,
132197             items = me.items,
132198             // At this point the item hasn't been removed from the items collection.
132199             // As such, if we want to check if there are no more tabs left, we have to
132200             // check for one, as opposed to 0.
132201             hasItemsLeft = items.getCount() > 1;
132202
132203         if (me.destroying || !hasItemsLeft) {
132204             me.activeTab = null;
132205         } else if (item === me.activeTab) {
132206              me.setActiveTab(item.next() || items.getAt(0));
132207         }
132208         me.callParent(arguments);
132209
132210         // Remove the two references
132211         delete item.tab.card;
132212         delete item.tab;
132213     },
132214
132215     /**
132216      * @ignore
132217      * Makes sure we remove the corresponding Tab when an item is removed
132218      */
132219     onRemove: function(item, autoDestroy) {
132220         var me = this;
132221
132222         item.un({
132223             scope : me,
132224             enable: me.onItemEnable,
132225             disable: me.onItemDisable,
132226             beforeshow: me.onItemBeforeShow
132227         });
132228         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
132229             me.tabBar.remove(item.tab);
132230         }
132231     }
132232 });
132233
132234 /**
132235  * A simple element that adds extra horizontal space between items in a toolbar.
132236  * By default a 2px wide space is added via CSS specification:
132237  *
132238  *     .x-toolbar .x-toolbar-spacer {
132239  *         width: 2px;
132240  *     }
132241  *
132242  * Example:
132243  *
132244  *     @example
132245  *     Ext.create('Ext.panel.Panel', {
132246  *         title: 'Toolbar Spacer Example',
132247  *         width: 300,
132248  *         height: 200,
132249  *         tbar : [
132250  *             'Item 1',
132251  *             { xtype: 'tbspacer' }, // or ' '
132252  *             'Item 2',
132253  *             // space width is also configurable via javascript
132254  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
132255  *             'Item 3'
132256  *         ],
132257  *         renderTo: Ext.getBody()
132258  *     });
132259  */
132260 Ext.define('Ext.toolbar.Spacer', {
132261     extend: 'Ext.Component',
132262     alias: 'widget.tbspacer',
132263     alternateClassName: 'Ext.Toolbar.Spacer',
132264     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
132265     focusable: false
132266 });
132267 /**
132268  * @class Ext.tree.Column
132269  * @extends Ext.grid.column.Column
132270  * 
132271  * Provides indentation and folder structure markup for a Tree taking into account
132272  * depth and position within the tree hierarchy.
132273  * 
132274  * @private
132275  */
132276 Ext.define('Ext.tree.Column', {
132277     extend: 'Ext.grid.column.Column',
132278     alias: 'widget.treecolumn',
132279
132280     initComponent: function() {
132281         var origRenderer = this.renderer || this.defaultRenderer,
132282             origScope    = this.scope || window;
132283
132284         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
132285             var buf   = [],
132286                 format = Ext.String.format,
132287                 depth = record.getDepth(),
132288                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
132289                 elbowPrefix = treePrefix + 'elbow-',
132290                 expanderCls = treePrefix + 'expander',
132291                 imgText     = '<img src="{1}" class="{0}" />',
132292                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
132293                 formattedValue = origRenderer.apply(origScope, arguments),
132294                 href = record.get('href'),
132295                 target = record.get('hrefTarget'),
132296                 cls = record.get('cls');
132297
132298             while (record) {
132299                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
132300                     if (record.getDepth() === depth) {
132301                         buf.unshift(format(imgText,
132302                             treePrefix + 'icon ' + 
132303                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
132304                             (record.get('iconCls') || ''),
132305                             record.get('icon') || Ext.BLANK_IMAGE_URL
132306                         ));
132307                         if (record.get('checked') !== null) {
132308                             buf.unshift(format(
132309                                 checkboxText,
132310                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
132311                                 record.get('checked') ? 'aria-checked="true"' : ''
132312                             ));
132313                             if (record.get('checked')) {
132314                                 metaData.tdCls += (' ' + treePrefix + 'checked');
132315                             }
132316                         }
132317                         if (record.isLast()) {
132318                             if (record.isExpandable()) {
132319                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
132320                             } else {
132321                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
132322                             }
132323                             
132324                         } else {
132325                             if (record.isExpandable()) {
132326                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
132327                             } else {
132328                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
132329                             }
132330                         }
132331                     } else {
132332                         if (record.isLast() || record.getDepth() === 0) {
132333                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
132334                         } else if (record.getDepth() !== 0) {
132335                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
132336                         }                      
132337                     }
132338                 }
132339                 record = record.parentNode;
132340             }
132341             if (href) {
132342                 buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
132343             } else {
132344                 buf.push(formattedValue);
132345             }
132346             if (cls) {
132347                 metaData.tdCls += ' ' + cls;
132348             }
132349             return buf.join('');
132350         };
132351         this.callParent(arguments);
132352     },
132353
132354     defaultRenderer: function(value) {
132355         return value;
132356     }
132357 });
132358 /**
132359  * Used as a view by {@link Ext.tree.Panel TreePanel}.
132360  */
132361 Ext.define('Ext.tree.View', {
132362     extend: 'Ext.view.Table',
132363     alias: 'widget.treeview',
132364
132365     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
132366     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
132367
132368     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
132369     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
132370     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
132371
132372     // Class to add to the node wrap element used to hold nodes when a parent is being
132373     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
132374     // for an ancestor node with this class.
132375     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
132376
132377     blockRefresh: true,
132378
132379     /** 
132380      * @cfg {Boolean} rootVisible
132381      * False to hide the root node.
132382      */
132383     rootVisible: true,
132384
132385     /** 
132386      * @cfg {Boolean} animate
132387      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
132388      */
132389
132390     expandDuration: 250,
132391     collapseDuration: 250,
132392     
132393     toggleOnDblClick: true,
132394
132395     initComponent: function() {
132396         var me = this;
132397         
132398         if (me.initialConfig.animate === undefined) {
132399             me.animate = Ext.enableFx;
132400         }
132401         
132402         me.store = Ext.create('Ext.data.NodeStore', {
132403             recursive: true,
132404             rootVisible: me.rootVisible,
132405             listeners: {
132406                 beforeexpand: me.onBeforeExpand,
132407                 expand: me.onExpand,
132408                 beforecollapse: me.onBeforeCollapse,
132409                 collapse: me.onCollapse,
132410                 scope: me
132411             }
132412         });
132413         
132414         if (me.node) {
132415             me.setRootNode(me.node);
132416         }
132417         me.animQueue = {};
132418         me.callParent(arguments);
132419     },
132420
132421     processUIEvent: function(e) {
132422         // If the clicked node is part of an animation, ignore the click.
132423         // This is because during a collapse animation, the associated Records
132424         // will already have been removed from the Store, and the event is not processable.
132425         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
132426             return false;
132427         }
132428         return this.callParent(arguments);
132429     },
132430
132431     onClear: function(){
132432         this.store.removeAll();    
132433     },
132434
132435     setRootNode: function(node) {
132436         var me = this;        
132437         me.store.setNode(node);
132438         me.node = node;
132439         if (!me.rootVisible) {
132440             node.expand();
132441         }
132442     },
132443     
132444     onRender: function() {
132445         var me = this,
132446             el;
132447
132448         me.callParent(arguments);
132449
132450         el = me.el;
132451         el.on({
132452             scope: me,
132453             delegate: me.expanderSelector,
132454             mouseover: me.onExpanderMouseOver,
132455             mouseout: me.onExpanderMouseOut
132456         });
132457         el.on({
132458             scope: me,
132459             delegate: me.checkboxSelector,
132460             click: me.onCheckboxChange
132461         });
132462     },
132463
132464     onCheckboxChange: function(e, t) {
132465         var me = this,
132466             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
132467             
132468         if (item) {
132469             me.onCheckChange(me.getRecord(item));
132470         }
132471     },
132472     
132473     onCheckChange: function(record){
132474         var checked = record.get('checked');
132475         if (Ext.isBoolean(checked)) {
132476             checked = !checked;
132477             record.set('checked', checked);
132478             this.fireEvent('checkchange', record, checked);
132479         }
132480     },
132481
132482     getChecked: function() {
132483         var checked = [];
132484         this.node.cascadeBy(function(rec){
132485             if (rec.get('checked')) {
132486                 checked.push(rec);
132487             }
132488         });
132489         return checked;
132490     },
132491     
132492     isItemChecked: function(rec){
132493         return rec.get('checked');
132494     },
132495
132496     createAnimWrap: function(record, index) {
132497         var thHtml = '',
132498             headerCt = this.panel.headerCt,
132499             headers = headerCt.getGridColumns(),
132500             i = 0, len = headers.length, item,
132501             node = this.getNode(record),
132502             tmpEl, nodeEl;
132503
132504         for (; i < len; i++) {
132505             item = headers[i];
132506             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
132507         }
132508
132509         nodeEl = Ext.get(node);        
132510         tmpEl = nodeEl.insertSibling({
132511             tag: 'tr',
132512             html: [
132513                 '<td colspan="' + headerCt.getColumnCount() + '">',
132514                     '<div class="' + this.nodeAnimWrapCls + '">',
132515                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
132516                             thHtml,
132517                         '</tbody></table>',
132518                     '</div>',
132519                 '</td>'
132520             ].join('')
132521         }, 'after');
132522
132523         return {
132524             record: record,
132525             node: node,
132526             el: tmpEl,
132527             expanding: false,
132528             collapsing: false,
132529             animating: false,
132530             animateEl: tmpEl.down('div'),
132531             targetEl: tmpEl.down('tbody')
132532         };
132533     },
132534
132535     getAnimWrap: function(parent) {
132536         if (!this.animate) {
132537             return null;
132538         }
132539
132540         // We are checking to see which parent is having the animation wrap
132541         while (parent) {
132542             if (parent.animWrap) {
132543                 return parent.animWrap;
132544             }
132545             parent = parent.parentNode;
132546         }
132547         return null;
132548     },
132549
132550     doAdd: function(nodes, records, index) {
132551         // If we are adding records which have a parent that is currently expanding
132552         // lets add them to the animation wrap
132553         var me = this,
132554             record = records[0],
132555             parent = record.parentNode,
132556             a = me.all.elements,
132557             relativeIndex = 0,
132558             animWrap = me.getAnimWrap(parent),
132559             targetEl, children, len;
132560
132561         if (!animWrap || !animWrap.expanding) {
132562             me.resetScrollers();
132563             return me.callParent(arguments);
132564         }
132565
132566         // We need the parent that has the animWrap, not the nodes parent
132567         parent = animWrap.record;
132568         
132569         // If there is an anim wrap we do our special magic logic
132570         targetEl = animWrap.targetEl;
132571         children = targetEl.dom.childNodes;
132572         
132573         // We subtract 1 from the childrens length because we have a tr in there with the th'es
132574         len = children.length - 1;
132575         
132576         // The relative index is the index in the full flat collection minus the index of the wraps parent
132577         relativeIndex = index - me.indexOf(parent) - 1;
132578         
132579         // If we are adding records to the wrap that have a higher relative index then there are currently children
132580         // it means we have to append the nodes to the wrap
132581         if (!len || relativeIndex >= len) {
132582             targetEl.appendChild(nodes);
132583         }
132584         // If there are already more children then the relative index it means we are adding child nodes of
132585         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
132586         else {
132587             // +1 because of the tr with th'es that is already there
132588             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
132589         }
132590
132591         // We also have to update the CompositeElementLite collection of the DataView
132592         Ext.Array.insert(a, index, nodes);
132593         
132594         // If we were in an animation we need to now change the animation
132595         // because the targetEl just got higher.
132596         if (animWrap.isAnimating) {
132597             me.onExpand(parent);
132598         }
132599     },
132600     
132601     beginBulkUpdate: function(){
132602         this.bulkUpdate = true;
132603         this.ownerCt.changingScrollbars = true;  
132604     },
132605     
132606     endBulkUpdate: function(){
132607         var me = this,
132608             ownerCt = me.ownerCt;
132609         
132610         me.bulkUpdate = false;
132611         me.ownerCt.changingScrollbars = true;  
132612         me.resetScrollers();  
132613     },
132614     
132615     onRemove : function(ds, record, index) {
132616         var me = this,
132617             bulk = me.bulkUpdate;
132618
132619         me.doRemove(record, index);
132620         if (!bulk) {
132621             me.updateIndexes(index);
132622         }
132623         if (me.store.getCount() === 0){
132624             me.refresh();
132625         }
132626         if (!bulk) {
132627             me.fireEvent('itemremove', record, index);
132628         }
132629     },
132630     
132631     doRemove: function(record, index) {
132632         // If we are adding records which have a parent that is currently expanding
132633         // lets add them to the animation wrap
132634         var me = this,
132635             parent = record.parentNode,
132636             all = me.all,
132637             animWrap = me.getAnimWrap(record),
132638             node = all.item(index).dom;
132639
132640         if (!animWrap || !animWrap.collapsing) {
132641             me.resetScrollers();
132642             return me.callParent(arguments);
132643         }
132644
132645         animWrap.targetEl.appendChild(node);
132646         all.removeElement(index);
132647     },
132648
132649     onBeforeExpand: function(parent, records, index) {
132650         var me = this,
132651             animWrap;
132652             
132653         if (!me.rendered || !me.animate) {
132654             return;
132655         }
132656
132657         if (me.getNode(parent)) {
132658             animWrap = me.getAnimWrap(parent);
132659             if (!animWrap) {
132660                 animWrap = parent.animWrap = me.createAnimWrap(parent);
132661                 animWrap.animateEl.setHeight(0);
132662             }
132663             else if (animWrap.collapsing) {
132664                 // If we expand this node while it is still expanding then we
132665                 // have to remove the nodes from the animWrap.
132666                 animWrap.targetEl.select(me.itemSelector).remove();
132667             } 
132668             animWrap.expanding = true;
132669             animWrap.collapsing = false;
132670         }
132671     },
132672
132673     onExpand: function(parent) {
132674         var me = this,
132675             queue = me.animQueue,
132676             id = parent.getId(),
132677             animWrap,
132678             animateEl, 
132679             targetEl,
132680             queueItem;        
132681         
132682         if (me.singleExpand) {
132683             me.ensureSingleExpand(parent);
132684         }
132685         
132686         animWrap = me.getAnimWrap(parent);
132687
132688         if (!animWrap) {
132689             me.resetScrollers();
132690             return;
132691         }
132692         
132693         animateEl = animWrap.animateEl;
132694         targetEl = animWrap.targetEl;
132695
132696         animateEl.stopAnimation();
132697         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
132698         queue[id] = true;
132699         animateEl.slideIn('t', {
132700             duration: me.expandDuration,
132701             listeners: {
132702                 scope: me,
132703                 lastframe: function() {
132704                     // Move all the nodes out of the anim wrap to their proper location
132705                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
132706                     animWrap.el.remove();
132707                     me.resetScrollers();
132708                     delete animWrap.record.animWrap;
132709                     delete queue[id];
132710                 }
132711             }
132712         });
132713         
132714         animWrap.isAnimating = true;
132715     },
132716     
132717     resetScrollers: function(){
132718         if (!this.bulkUpdate) {
132719             var panel = this.panel;
132720             
132721             panel.determineScrollbars();
132722             panel.invalidateScroller();
132723         }
132724     },
132725
132726     onBeforeCollapse: function(parent, records, index) {
132727         var me = this,
132728             animWrap;
132729             
132730         if (!me.rendered || !me.animate) {
132731             return;
132732         }
132733
132734         if (me.getNode(parent)) {
132735             animWrap = me.getAnimWrap(parent);
132736             if (!animWrap) {
132737                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
132738             }
132739             else if (animWrap.expanding) {
132740                 // If we collapse this node while it is still expanding then we
132741                 // have to remove the nodes from the animWrap.
132742                 animWrap.targetEl.select(this.itemSelector).remove();
132743             }
132744             animWrap.expanding = false;
132745             animWrap.collapsing = true;
132746         }
132747     },
132748     
132749     onCollapse: function(parent) {
132750         var me = this,
132751             queue = me.animQueue,
132752             id = parent.getId(),
132753             animWrap = me.getAnimWrap(parent),
132754             animateEl, targetEl;
132755
132756         if (!animWrap) {
132757             me.resetScrollers();
132758             return;
132759         }
132760         
132761         animateEl = animWrap.animateEl;
132762         targetEl = animWrap.targetEl;
132763
132764         queue[id] = true;
132765         
132766         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
132767         animateEl.stopAnimation();
132768         animateEl.slideOut('t', {
132769             duration: me.collapseDuration,
132770             listeners: {
132771                 scope: me,
132772                 lastframe: function() {
132773                     animWrap.el.remove();
132774                     delete animWrap.record.animWrap;
132775                     me.resetScrollers();
132776                     delete queue[id];
132777                 }             
132778             }
132779         });
132780         animWrap.isAnimating = true;
132781     },
132782     
132783     /**
132784      * Checks if a node is currently undergoing animation
132785      * @private
132786      * @param {Ext.data.Model} node The node
132787      * @return {Boolean} True if the node is animating
132788      */
132789     isAnimating: function(node) {
132790         return !!this.animQueue[node.getId()];    
132791     },
132792     
132793     collectData: function(records) {
132794         var data = this.callParent(arguments),
132795             rows = data.rows,
132796             len = rows.length,
132797             i = 0,
132798             row, record;
132799             
132800         for (; i < len; i++) {
132801             row = rows[i];
132802             record = records[i];
132803             if (record.get('qtip')) {
132804                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
132805                 if (record.get('qtitle')) {
132806                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
132807                 }
132808             }
132809             if (record.isExpanded()) {
132810                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
132811             }
132812             if (record.isLoading()) {
132813                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
132814             }
132815         }
132816         
132817         return data;
132818     },
132819     
132820     /**
132821      * Expands a record that is loaded in the view.
132822      * @param {Ext.data.Model} record The record to expand
132823      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
132824      * @param {Function} callback (optional) The function to run after the expand is completed
132825      * @param {Object} scope (optional) The scope of the callback function.
132826      */
132827     expand: function(record, deep, callback, scope) {
132828         return record.expand(deep, callback, scope);
132829     },
132830     
132831     /**
132832      * Collapses a record that is loaded in the view.
132833      * @param {Ext.data.Model} record The record to collapse
132834      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
132835      * @param {Function} callback (optional) The function to run after the collapse is completed
132836      * @param {Object} scope (optional) The scope of the callback function.
132837      */
132838     collapse: function(record, deep, callback, scope) {
132839         return record.collapse(deep, callback, scope);
132840     },
132841     
132842     /**
132843      * Toggles a record between expanded and collapsed.
132844      * @param {Ext.data.Model} recordInstance
132845      */
132846     toggle: function(record) {
132847         this[record.isExpanded() ? 'collapse' : 'expand'](record);
132848     },
132849     
132850     onItemDblClick: function(record, item, index) {
132851         this.callParent(arguments);
132852         if (this.toggleOnDblClick) {
132853             this.toggle(record);
132854         }
132855     },
132856     
132857     onBeforeItemMouseDown: function(record, item, index, e) {
132858         if (e.getTarget(this.expanderSelector, item)) {
132859             return false;
132860         }
132861         return this.callParent(arguments);
132862     },
132863     
132864     onItemClick: function(record, item, index, e) {
132865         if (e.getTarget(this.expanderSelector, item)) {
132866             this.toggle(record);
132867             return false;
132868         }
132869         return this.callParent(arguments);
132870     },
132871     
132872     onExpanderMouseOver: function(e, t) {
132873         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
132874     },
132875     
132876     onExpanderMouseOut: function(e, t) {
132877         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
132878     },
132879     
132880     /**
132881      * Gets the base TreeStore from the bound TreePanel.
132882      */
132883     getTreeStore: function() {
132884         return this.panel.store;
132885     },    
132886     
132887     ensureSingleExpand: function(node) {
132888         var parent = node.parentNode;
132889         if (parent) {
132890             parent.eachChild(function(child) {
132891                 if (child !== node && child.isExpanded()) {
132892                     child.collapse();
132893                 }
132894             });
132895         }
132896     }
132897 });
132898 /**
132899  * The TreePanel provides tree-structured UI representation of tree-structured data.
132900  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
132901  * multiple columns through the {@link #columns} configuration.
132902  *
132903  * Simple TreePanel using inline data:
132904  *
132905  *     @example
132906  *     var store = Ext.create('Ext.data.TreeStore', {
132907  *         root: {
132908  *             expanded: true,
132909  *             children: [
132910  *                 { text: "detention", leaf: true },
132911  *                 { text: "homework", expanded: true, children: [
132912  *                     { text: "book report", leaf: true },
132913  *                     { text: "alegrbra", leaf: true}
132914  *                 ] },
132915  *                 { text: "buy lottery tickets", leaf: true }
132916  *             ]
132917  *         }
132918  *     });
132919  *
132920  *     Ext.create('Ext.tree.Panel', {
132921  *         title: 'Simple Tree',
132922  *         width: 200,
132923  *         height: 150,
132924  *         store: store,
132925  *         rootVisible: false,
132926  *         renderTo: Ext.getBody()
132927  *     });
132928  *
132929  * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
132930  * {@link Ext.data.NodeInterface NodeInterface} config options.
132931  */
132932 Ext.define('Ext.tree.Panel', {
132933     extend: 'Ext.panel.Table',
132934     alias: 'widget.treepanel',
132935     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
132936     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
132937     viewType: 'treeview',
132938     selType: 'treemodel',
132939
132940     treeCls: Ext.baseCSSPrefix + 'tree-panel',
132941
132942     deferRowRender: false,
132943
132944     /**
132945      * @cfg {Boolean} lines False to disable tree lines.
132946      */
132947     lines: true,
132948
132949     /**
132950      * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
132951      */
132952     useArrows: false,
132953
132954     /**
132955      * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
132956      */
132957     singleExpand: false,
132958
132959     ddConfig: {
132960         enableDrag: true,
132961         enableDrop: true
132962     },
132963
132964     /**
132965      * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
132966      */
132967
132968     /**
132969      * @cfg {Boolean} rootVisible False to hide the root node.
132970      */
132971     rootVisible: true,
132972
132973     /**
132974      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
132975      */
132976     displayField: 'text',
132977
132978     /**
132979      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
132980      * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
132981      * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
132982      * to that store. For example:
132983      *
132984      *     Ext.create('Ext.tree.Panel', {
132985      *         title: 'Simple Tree',
132986      *         root: {
132987      *             text: "Root node",
132988      *             expanded: true,
132989      *             children: [
132990      *                 { text: "Child 1", leaf: true },
132991      *                 { text: "Child 2", leaf: true }
132992      *             ]
132993      *         },
132994      *         renderTo: Ext.getBody()
132995      *     });
132996      */
132997     root: null,
132998
132999     // Required for the Lockable Mixin. These are the configurations which will be copied to the
133000     // normal and locked sub tablepanels
133001     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
133002     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
133003
133004     /**
133005      * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
133006      */
133007
133008     /**
133009      * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
133010      */
133011
133012     constructor: function(config) {
133013         config = config || {};
133014         if (config.animate === undefined) {
133015             config.animate = Ext.enableFx;
133016         }
133017         this.enableAnimations = config.animate;
133018         delete config.animate;
133019
133020         this.callParent([config]);
133021     },
133022
133023     initComponent: function() {
133024         var me = this,
133025             cls = [me.treeCls];
133026
133027         if (me.useArrows) {
133028             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
133029             me.lines = false;
133030         }
133031
133032         if (me.lines) {
133033             cls.push(Ext.baseCSSPrefix + 'tree-lines');
133034         } else if (!me.useArrows) {
133035             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
133036         }
133037
133038         if (Ext.isString(me.store)) {
133039             me.store = Ext.StoreMgr.lookup(me.store);
133040         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
133041             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
133042                 root: me.root,
133043                 fields: me.fields,
133044                 model: me.model,
133045                 folderSort: me.folderSort
133046             }));
133047         } else if (me.root) {
133048             me.store = Ext.data.StoreManager.lookup(me.store);
133049             me.store.setRootNode(me.root);
133050             if (me.folderSort !== undefined) {
133051                 me.store.folderSort = me.folderSort;
133052                 me.store.sort();
133053             }
133054         }
133055
133056         // I'm not sure if we want to this. It might be confusing
133057         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
133058         //     me.rootVisible = false;
133059         // }
133060
133061         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
133062             rootVisible: me.rootVisible,
133063             animate: me.enableAnimations,
133064             singleExpand: me.singleExpand,
133065             node: me.store.getRootNode(),
133066             hideHeaders: me.hideHeaders
133067         });
133068
133069         me.mon(me.store, {
133070             scope: me,
133071             rootchange: me.onRootChange,
133072             clear: me.onClear
133073         });
133074
133075         me.relayEvents(me.store, [
133076             /**
133077              * @event beforeload
133078              * @alias Ext.data.Store#beforeload
133079              */
133080             'beforeload',
133081
133082             /**
133083              * @event load
133084              * @alias Ext.data.Store#load
133085              */
133086             'load'
133087         ]);
133088
133089         me.store.on({
133090             /**
133091              * @event itemappend
133092              * @alias Ext.data.TreeStore#append
133093              */
133094             append: me.createRelayer('itemappend'),
133095
133096             /**
133097              * @event itemremove
133098              * @alias Ext.data.TreeStore#remove
133099              */
133100             remove: me.createRelayer('itemremove'),
133101
133102             /**
133103              * @event itemmove
133104              * @alias Ext.data.TreeStore#move
133105              */
133106             move: me.createRelayer('itemmove'),
133107
133108             /**
133109              * @event iteminsert
133110              * @alias Ext.data.TreeStore#insert
133111              */
133112             insert: me.createRelayer('iteminsert'),
133113
133114             /**
133115              * @event beforeitemappend
133116              * @alias Ext.data.TreeStore#beforeappend
133117              */
133118             beforeappend: me.createRelayer('beforeitemappend'),
133119
133120             /**
133121              * @event beforeitemremove
133122              * @alias Ext.data.TreeStore#beforeremove
133123              */
133124             beforeremove: me.createRelayer('beforeitemremove'),
133125
133126             /**
133127              * @event beforeitemmove
133128              * @alias Ext.data.TreeStore#beforemove
133129              */
133130             beforemove: me.createRelayer('beforeitemmove'),
133131
133132             /**
133133              * @event beforeiteminsert
133134              * @alias Ext.data.TreeStore#beforeinsert
133135              */
133136             beforeinsert: me.createRelayer('beforeiteminsert'),
133137
133138             /**
133139              * @event itemexpand
133140              * @alias Ext.data.TreeStore#expand
133141              */
133142             expand: me.createRelayer('itemexpand'),
133143
133144             /**
133145              * @event itemcollapse
133146              * @alias Ext.data.TreeStore#collapse
133147              */
133148             collapse: me.createRelayer('itemcollapse'),
133149
133150             /**
133151              * @event beforeitemexpand
133152              * @alias Ext.data.TreeStore#beforeexpand
133153              */
133154             beforeexpand: me.createRelayer('beforeitemexpand'),
133155
133156             /**
133157              * @event beforeitemcollapse
133158              * @alias Ext.data.TreeStore#beforecollapse
133159              */
133160             beforecollapse: me.createRelayer('beforeitemcollapse')
133161         });
133162
133163         // If the user specifies the headers collection manually then dont inject our own
133164         if (!me.columns) {
133165             if (me.initialConfig.hideHeaders === undefined) {
133166                 me.hideHeaders = true;
133167             }
133168             me.columns = [{
133169                 xtype    : 'treecolumn',
133170                 text     : 'Name',
133171                 flex     : 1,
133172                 dataIndex: me.displayField
133173             }];
133174         }
133175
133176         if (me.cls) {
133177             cls.push(me.cls);
133178         }
133179         me.cls = cls.join(' ');
133180         me.callParent();
133181
133182         me.relayEvents(me.getView(), [
133183             /**
133184              * @event checkchange
133185              * Fires when a node with a checkbox's checked property changes
133186              * @param {Ext.data.Model} node The node who's checked property was changed
133187              * @param {Boolean} checked The node's new checked state
133188              */
133189             'checkchange'
133190         ]);
133191
133192         // If the root is not visible and there is no rootnode defined, then just lets load the store
133193         if (!me.getView().rootVisible && !me.getRootNode()) {
133194             me.setRootNode({
133195                 expanded: true
133196             });
133197         }
133198     },
133199
133200     onClear: function(){
133201         this.view.onClear();
133202     },
133203
133204     /**
133205      * Sets root node of this tree.
133206      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
133207      * @return {Ext.data.NodeInterface} The new root
133208      */
133209     setRootNode: function() {
133210         return this.store.setRootNode.apply(this.store, arguments);
133211     },
133212
133213     /**
133214      * Returns the root node for this tree.
133215      * @return {Ext.data.NodeInterface}
133216      */
133217     getRootNode: function() {
133218         return this.store.getRootNode();
133219     },
133220
133221     onRootChange: function(root) {
133222         this.view.setRootNode(root);
133223     },
133224
133225     /**
133226      * Retrieve an array of checked records.
133227      * @return {Ext.data.Model[]} An array containing the checked records
133228      */
133229     getChecked: function() {
133230         return this.getView().getChecked();
133231     },
133232
133233     isItemChecked: function(rec) {
133234         return rec.get('checked');
133235     },
133236
133237     /**
133238      * Expand all nodes
133239      * @param {Function} callback (optional) A function to execute when the expand finishes.
133240      * @param {Object} scope (optional) The scope of the callback function
133241      */
133242     expandAll : function(callback, scope) {
133243         var root = this.getRootNode(),
133244             animate = this.enableAnimations,
133245             view = this.getView();
133246         if (root) {
133247             if (!animate) {
133248                 view.beginBulkUpdate();
133249             }
133250             root.expand(true, callback, scope);
133251             if (!animate) {
133252                 view.endBulkUpdate();
133253             }
133254         }
133255     },
133256
133257     /**
133258      * Collapse all nodes
133259      * @param {Function} callback (optional) A function to execute when the collapse finishes.
133260      * @param {Object} scope (optional) The scope of the callback function
133261      */
133262     collapseAll : function(callback, scope) {
133263         var root = this.getRootNode(),
133264             animate = this.enableAnimations,
133265             view = this.getView();
133266
133267         if (root) {
133268             if (!animate) {
133269                 view.beginBulkUpdate();
133270             }
133271             if (view.rootVisible) {
133272                 root.collapse(true, callback, scope);
133273             } else {
133274                 root.collapseChildren(true, callback, scope);
133275             }
133276             if (!animate) {
133277                 view.endBulkUpdate();
133278             }
133279         }
133280     },
133281
133282     /**
133283      * Expand the tree to the path of a particular node.
133284      * @param {String} path The path to expand. The path should include a leading separator.
133285      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
133286      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
133287      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
133288      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
133289      * @param {Object} scope (optional) The scope of the callback function
133290      */
133291     expandPath: function(path, field, separator, callback, scope) {
133292         var me = this,
133293             current = me.getRootNode(),
133294             index = 1,
133295             view = me.getView(),
133296             keys,
133297             expander;
133298
133299         field = field || me.getRootNode().idProperty;
133300         separator = separator || '/';
133301
133302         if (Ext.isEmpty(path)) {
133303             Ext.callback(callback, scope || me, [false, null]);
133304             return;
133305         }
133306
133307         keys = path.split(separator);
133308         if (current.get(field) != keys[1]) {
133309             // invalid root
133310             Ext.callback(callback, scope || me, [false, current]);
133311             return;
133312         }
133313
133314         expander = function(){
133315             if (++index === keys.length) {
133316                 Ext.callback(callback, scope || me, [true, current]);
133317                 return;
133318             }
133319             var node = current.findChild(field, keys[index]);
133320             if (!node) {
133321                 Ext.callback(callback, scope || me, [false, current]);
133322                 return;
133323             }
133324             current = node;
133325             current.expand(false, expander);
133326         };
133327         current.expand(false, expander);
133328     },
133329
133330     /**
133331      * Expand the tree to the path of a particular node, then select it.
133332      * @param {String} path The path to select. The path should include a leading separator.
133333      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
133334      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
133335      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
133336      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
133337      * @param {Object} scope (optional) The scope of the callback function
133338      */
133339     selectPath: function(path, field, separator, callback, scope) {
133340         var me = this,
133341             keys,
133342             last;
133343
133344         field = field || me.getRootNode().idProperty;
133345         separator = separator || '/';
133346
133347         keys = path.split(separator);
133348         last = keys.pop();
133349
133350         me.expandPath(keys.join(separator), field, separator, function(success, node){
133351             var doSuccess = false;
133352             if (success && node) {
133353                 node = node.findChild(field, last);
133354                 if (node) {
133355                     me.getSelectionModel().select(node);
133356                     Ext.callback(callback, scope || me, [true, node]);
133357                     doSuccess = true;
133358                 }
133359             } else if (node === me.getRootNode()) {
133360                 doSuccess = true;
133361             }
133362             Ext.callback(callback, scope || me, [doSuccess, node]);
133363         }, me);
133364     }
133365 });
133366
133367 /**
133368  * @class Ext.view.DragZone
133369  * @extends Ext.dd.DragZone
133370  * @private
133371  */
133372 Ext.define('Ext.view.DragZone', {
133373     extend: 'Ext.dd.DragZone',
133374     containerScroll: false,
133375
133376     constructor: function(config) {
133377         var me = this;
133378
133379         Ext.apply(me, config);
133380
133381         // Create a ddGroup unless one has been configured.
133382         // User configuration of ddGroups allows users to specify which
133383         // DD instances can interact with each other. Using one
133384         // based on the id of the View would isolate it and mean it can only
133385         // interact with a DropZone on the same View also using a generated ID.
133386         if (!me.ddGroup) {
133387             me.ddGroup = 'view-dd-zone-' + me.view.id;
133388         }
133389
133390         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
133391         // So a View's DragZone cannot use the View's main element because the DropZone must use that
133392         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
133393         // main element which handles scrolling.
133394         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
133395         // is transient; DataView's recreate the internal structure dynamically as data changes.
133396         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
133397         me.callParent([me.view.el.dom.parentNode]);
133398
133399         me.ddel = Ext.get(document.createElement('div'));
133400         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
133401     },
133402
133403     init: function(id, sGroup, config) {
133404         this.initTarget(id, sGroup, config);
133405         this.view.mon(this.view, {
133406             itemmousedown: this.onItemMouseDown,
133407             scope: this
133408         });
133409     },
133410
133411     onItemMouseDown: function(view, record, item, index, e) {
133412         if (!this.isPreventDrag(e, record, item, index)) {
133413             this.handleMouseDown(e);
133414
133415             // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
133416             // if the mousedowned record is selected
133417             if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
133418                 return false;
133419             }
133420         }
133421     },
133422
133423     // private template method
133424     isPreventDrag: function(e) {
133425         return false;
133426     },
133427
133428     getDragData: function(e) {
133429         var view = this.view,
133430             item = e.getTarget(view.getItemSelector()),
133431             record, selectionModel, records;
133432
133433         if (item) {
133434             record = view.getRecord(item);
133435             selectionModel = view.getSelectionModel();
133436             records = selectionModel.getSelection();
133437             return {
133438                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
133439                 event: new Ext.EventObjectImpl(e),
133440                 view: view,
133441                 ddel: this.ddel,
133442                 item: item,
133443                 records: records,
133444                 fromPosition: Ext.fly(item).getXY()
133445             };
133446         }
133447     },
133448
133449     onInitDrag: function(x, y) {
133450         var me = this,
133451             data = me.dragData,
133452             view = data.view,
133453             selectionModel = view.getSelectionModel(),
133454             record = view.getRecord(data.item),
133455             e = data.event;
133456
133457         // Update the selection to match what would have been selected if the user had
133458         // done a full click on the target node rather than starting a drag from it
133459         if (!selectionModel.isSelected(record) || e.hasModifier()) {
133460             selectionModel.selectWithEvent(record, e, true);
133461         }
133462         data.records = selectionModel.getSelection();
133463
133464         me.ddel.update(me.getDragText());
133465         me.proxy.update(me.ddel.dom);
133466         me.onStartDrag(x, y);
133467         return true;
133468     },
133469
133470     getDragText: function() {
133471         var count = this.dragData.records.length;
133472         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
133473     },
133474
133475     getRepairXY : function(e, data){
133476         return data ? data.fromPosition : false;
133477     }
133478 });
133479 Ext.define('Ext.tree.ViewDragZone', {
133480     extend: 'Ext.view.DragZone',
133481
133482     isPreventDrag: function(e, record) {
133483         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
133484     },
133485     
133486     afterRepair: function() {
133487         var me = this,
133488             view = me.view,
133489             selectedRowCls = view.selectedItemCls,
133490             records = me.dragData.records,
133491             fly = Ext.fly;
133492         
133493         if (Ext.enableFx && me.repairHighlight) {
133494             // Roll through all records and highlight all the ones we attempted to drag.
133495             Ext.Array.forEach(records, function(record) {
133496                 // anonymous fns below, don't hoist up unless below is wrapped in
133497                 // a self-executing function passing in item.
133498                 var item = view.getNode(record);
133499                 
133500                 // We must remove the selected row class before animating, because
133501                 // the selected row class declares !important on its background-color.
133502                 fly(item.firstChild).highlight(me.repairHighlightColor, {
133503                     listeners: {
133504                         beforeanimate: function() {
133505                             if (view.isSelected(item)) {
133506                                 fly(item).removeCls(selectedRowCls);
133507                             }
133508                         },
133509                         afteranimate: function() {
133510                             if (view.isSelected(item)) {
133511                                 fly(item).addCls(selectedRowCls);
133512                             }
133513                         }
133514                     }
133515                 });
133516             });
133517         }
133518         me.dragging = false;
133519     }
133520 });
133521 /**
133522  * @class Ext.tree.ViewDropZone
133523  * @extends Ext.view.DropZone
133524  * @private
133525  */
133526 Ext.define('Ext.tree.ViewDropZone', {
133527     extend: 'Ext.view.DropZone',
133528
133529     /**
133530      * @cfg {Boolean} allowParentInsert
133531      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
133532      * sibling of the parent when dropped.
133533      */
133534     allowParentInserts: false,
133535  
133536     /**
133537      * @cfg {String} allowContainerDrop
133538      * True if drops on the tree container (outside of a specific tree node) are allowed.
133539      */
133540     allowContainerDrops: false,
133541
133542     /**
133543      * @cfg {String} appendOnly
133544      * True if the tree should only allow append drops (use for trees which are sorted).
133545      */
133546     appendOnly: false,
133547
133548     /**
133549      * @cfg {String} expandDelay
133550      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
133551      * over the target.
133552      */
133553     expandDelay : 500,
133554
133555     indicatorCls: 'x-tree-ddindicator',
133556
133557     // private
133558     expandNode : function(node) {
133559         var view = this.view;
133560         if (!node.isLeaf() && !node.isExpanded()) {
133561             view.expand(node);
133562             this.expandProcId = false;
133563         }
133564     },
133565
133566     // private
133567     queueExpand : function(node) {
133568         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
133569     },
133570
133571     // private
133572     cancelExpand : function() {
133573         if (this.expandProcId) {
133574             clearTimeout(this.expandProcId);
133575             this.expandProcId = false;
133576         }
133577     },
133578
133579     getPosition: function(e, node) {
133580         var view = this.view,
133581             record = view.getRecord(node),
133582             y = e.getPageY(),
133583             noAppend = record.isLeaf(),
133584             noBelow = false,
133585             region = Ext.fly(node).getRegion(),
133586             fragment;
133587
133588         // If we are dragging on top of the root node of the tree, we always want to append.
133589         if (record.isRoot()) {
133590             return 'append';
133591         }
133592
133593         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
133594         if (this.appendOnly) {
133595             return noAppend ? false : 'append';
133596         }
133597
133598         if (!this.allowParentInsert) {
133599             noBelow = record.hasChildNodes() && record.isExpanded();
133600         }
133601
133602         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
133603         if (y >= region.top && y < (region.top + fragment)) {
133604             return 'before';
133605         }
133606         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
133607             return 'after';
133608         }
133609         else {
133610             return 'append';
133611         }
133612     },
133613
133614     isValidDropPoint : function(node, position, dragZone, e, data) {
133615         if (!node || !data.item) {
133616             return false;
133617         }
133618
133619         var view = this.view,
133620             targetNode = view.getRecord(node),
133621             draggedRecords = data.records,
133622             dataLength = draggedRecords.length,
133623             ln = draggedRecords.length,
133624             i, record;
133625
133626         // No drop position, or dragged records: invalid drop point
133627         if (!(targetNode && position && dataLength)) {
133628             return false;
133629         }
133630
133631         // If the targetNode is within the folder we are dragging
133632         for (i = 0; i < ln; i++) {
133633             record = draggedRecords[i];
133634             if (record.isNode && record.contains(targetNode)) {
133635                 return false;
133636             }
133637         }
133638         
133639         // Respect the allowDrop field on Tree nodes
133640         if (position === 'append' && targetNode.get('allowDrop') === false) {
133641             return false;
133642         }
133643         else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
133644             return false;
133645         }
133646
133647         // If the target record is in the dragged dataset, then invalid drop
133648         if (Ext.Array.contains(draggedRecords, targetNode)) {
133649              return false;
133650         }
133651
133652         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
133653         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
133654         return true;
133655     },
133656
133657     onNodeOver : function(node, dragZone, e, data) {
133658         var position = this.getPosition(e, node),
133659             returnCls = this.dropNotAllowed,
133660             view = this.view,
133661             targetNode = view.getRecord(node),
133662             indicator = this.getIndicator(),
133663             indicatorX = 0,
133664             indicatorY = 0;
133665
133666         // auto node expand check
133667         this.cancelExpand();
133668         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
133669             this.queueExpand(targetNode);
133670         }
133671             
133672             
133673         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
133674             this.valid = true;
133675             this.currentPosition = position;
133676             this.overRecord = targetNode;
133677
133678             indicator.setWidth(Ext.fly(node).getWidth());
133679             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
133680
133681             /*
133682              * In the code below we show the proxy again. The reason for doing this is showing the indicator will
133683              * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always 
133684              * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
133685              */
133686             if (position == 'before') {
133687                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
133688                 indicator.showAt(0, indicatorY);
133689                 dragZone.proxy.show();
133690             } else if (position == 'after') {
133691                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
133692                 indicatorY += Ext.fly(node).getHeight();
133693                 indicator.showAt(0, indicatorY);
133694                 dragZone.proxy.show();
133695             } else {
133696                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
133697                 // @TODO: set a class on the parent folder node to be able to style it
133698                 indicator.hide();
133699             }
133700         } else {
133701             this.valid = false;
133702         }
133703
133704         this.currentCls = returnCls;
133705         return returnCls;
133706     },
133707
133708     onContainerOver : function(dd, e, data) {
133709         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
133710     },
133711     
133712     notifyOut: function() {
133713         this.callParent(arguments);
133714         this.cancelExpand();
133715     },
133716
133717     handleNodeDrop : function(data, targetNode, position) {
133718         var me = this,
133719             view = me.view,
133720             parentNode = targetNode.parentNode,
133721             store = view.getStore(),
133722             recordDomNodes = [],
133723             records, i, len,
133724             insertionMethod, argList,
133725             needTargetExpand,
133726             transferData,
133727             processDrop;
133728
133729         // If the copy flag is set, create a copy of the Models with the same IDs
133730         if (data.copy) {
133731             records = data.records;
133732             data.records = [];
133733             for (i = 0, len = records.length; i < len; i++) {
133734                 data.records.push(Ext.apply({}, records[i].data));
133735             }
133736         }
133737
133738         // Cancel any pending expand operation
133739         me.cancelExpand();
133740
133741         // Grab a reference to the correct node insertion method.
133742         // Create an arg list array intended for the apply method of the
133743         // chosen node insertion method.
133744         // Ensure the target object for the method is referenced by 'targetNode'
133745         if (position == 'before') {
133746             insertionMethod = parentNode.insertBefore;
133747             argList = [null, targetNode];
133748             targetNode = parentNode;
133749         }
133750         else if (position == 'after') {
133751             if (targetNode.nextSibling) {
133752                 insertionMethod = parentNode.insertBefore;
133753                 argList = [null, targetNode.nextSibling];
133754             }
133755             else {
133756                 insertionMethod = parentNode.appendChild;
133757                 argList = [null];
133758             }
133759             targetNode = parentNode;
133760         }
133761         else {
133762             if (!targetNode.isExpanded()) {
133763                 needTargetExpand = true;
133764             }
133765             insertionMethod = targetNode.appendChild;
133766             argList = [null];
133767         }
133768
133769         // A function to transfer the data into the destination tree
133770         transferData = function() {
133771             var node;
133772             for (i = 0, len = data.records.length; i < len; i++) {
133773                 argList[0] = data.records[i];
133774                 node = insertionMethod.apply(targetNode, argList);
133775                 
133776                 if (Ext.enableFx && me.dropHighlight) {
133777                     recordDomNodes.push(view.getNode(node));
133778                 }
133779             }
133780             
133781             // Kick off highlights after everything's been inserted, so they are
133782             // more in sync without insertion/render overhead.
133783             if (Ext.enableFx && me.dropHighlight) {
133784                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
133785                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
133786                 Ext.Array.forEach(recordDomNodes, function(n) {
133787                     if (n) {
133788                         Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
133789                     }
133790                 });
133791             }
133792         };
133793
133794         // If dropping right on an unexpanded node, transfer the data after it is expanded.
133795         if (needTargetExpand) {
133796             targetNode.expand(false, transferData);
133797         }
133798         // Otherwise, call the data transfer function immediately
133799         else {
133800             transferData();
133801         }
133802     }
133803 });
133804 /**
133805  * This plugin provides drag and/or drop functionality for a TreeView.
133806  *
133807  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
133808  * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
133809  * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
133810  *
133811  *   - copy : Boolean
133812  *
133813  *     The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
133814  *     the control key was pressed when the drag operation was begun.
133815  *
133816  *   - view : TreeView
133817  *
133818  *     The source TreeView from which the drag originated.
133819  *
133820  *   - ddel : HtmlElement
133821  *
133822  *     The drag proxy element which moves with the mouse
133823  *
133824  *   - item : HtmlElement
133825  *
133826  *     The TreeView node upon which the mousedown event was registered.
133827  *
133828  *   - records : Array
133829  *
133830  *     An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
133831  *
133832  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
133833  * members of the same ddGroup which processes such data objects.
133834  *
133835  * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
133836  * {@link #drop}.
133837  *
133838  * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
133839  *
133840  *     viewConfig: {
133841  *         plugins: { ptype: 'treeviewdragdrop' }
133842  *     }
133843  */
133844 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
133845     extend: 'Ext.AbstractPlugin',
133846     alias: 'plugin.treeviewdragdrop',
133847
133848     uses: [
133849         'Ext.tree.ViewDragZone',
133850         'Ext.tree.ViewDropZone'
133851     ],
133852
133853     /**
133854      * @event beforedrop
133855      *
133856      * **This event is fired through the TreeView. Add listeners to the TreeView object**
133857      *
133858      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
133859      *
133860      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133861      *
133862      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
133863      * back to the point from which the drag began.
133864      *
133865      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
133866      * was valid, and that the repair operation should not take place.
133867      *
133868      * Any other return value continues with the data transfer operation.
133869      *
133870      * @param {Object} data The data object gathered at mousedown time by the cooperating
133871      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133872      * properties:
133873      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133874      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133875      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133876      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133877      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133878      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133879      * dragged from the source TreeView.
133880      *
133881      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133882      *
133883      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133884      * the midline of the node, or the node is a branch node which accepts new child nodes.
133885      *
133886      * @param {Function} dropFunction A function to call to complete the data transfer operation and either move or copy
133887      * Model instances from the source View's Store to the destination View's Store.
133888      *
133889      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
133890      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
133891      *
133892      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
133893      */
133894
133895     /**
133896      * @event drop
133897      *
133898      * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
133899      * has been completed and the data has been moved or copied.
133900      *
133901      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133902      *
133903      * @param {Object} data The data object gathered at mousedown time by the cooperating
133904      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133905      * properties:
133906      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133907      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133908      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133909      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133910      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133911      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133912      * dragged from the source TreeView.
133913      *
133914      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133915      *
133916      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133917      * the midline of the node, or the node is a branch node which accepts new child nodes.
133918      */
133919
133920     dragText : '{0} selected node{1}',
133921
133922     /**
133923      * @cfg {Boolean} allowParentInsert
133924      * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
133925      * the parent when dropped.
133926      */
133927     allowParentInserts: false,
133928
133929     /**
133930      * @cfg {String} allowContainerDrop
133931      * True if drops on the tree container (outside of a specific tree node) are allowed.
133932      */
133933     allowContainerDrops: false,
133934
133935     /**
133936      * @cfg {String} appendOnly
133937      * True if the tree should only allow append drops (use for trees which are sorted).
133938      */
133939     appendOnly: false,
133940
133941     /**
133942      * @cfg {String} ddGroup
133943      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
133944      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
133945      */
133946     ddGroup : "TreeDD",
133947
133948     /**
133949      * @cfg {String} dragGroup
133950      * The ddGroup to which the DragZone will belong.
133951      *
133952      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
133953      * Drag/DropZones which are members of the same ddGroup.
133954      */
133955
133956     /**
133957      * @cfg {String} dropGroup
133958      * The ddGroup to which the DropZone will belong.
133959      *
133960      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
133961      * Drag/DropZones which are members of the same ddGroup.
133962      */
133963
133964     /**
133965      * @cfg {String} expandDelay
133966      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
133967      * target.
133968      */
133969     expandDelay : 1000,
133970
133971     /**
133972      * @cfg {Boolean} enableDrop
133973      * Set to `false` to disallow the View from accepting drop gestures.
133974      */
133975     enableDrop: true,
133976
133977     /**
133978      * @cfg {Boolean} enableDrag
133979      * Set to `false` to disallow dragging items from the View.
133980      */
133981     enableDrag: true,
133982
133983     /**
133984      * @cfg {String} nodeHighlightColor
133985      * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
133986      * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
133987      * {@link #nodeHighlightOnRepair}.
133988      */
133989     nodeHighlightColor: 'c3daf9',
133990
133991     /**
133992      * @cfg {Boolean} nodeHighlightOnDrop
133993      * Whether or not to highlight any nodes after they are
133994      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
133995      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
133996      */
133997     nodeHighlightOnDrop: Ext.enableFx,
133998
133999     /**
134000      * @cfg {Boolean} nodeHighlightOnRepair
134001      * Whether or not to highlight any nodes after they are
134002      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
134003      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
134004      */
134005     nodeHighlightOnRepair: Ext.enableFx,
134006
134007     init : function(view) {
134008         view.on('render', this.onViewRender, this, {single: true});
134009     },
134010
134011     /**
134012      * @private
134013      * AbstractComponent calls destroy on all its plugins at destroy time.
134014      */
134015     destroy: function() {
134016         Ext.destroy(this.dragZone, this.dropZone);
134017     },
134018
134019     onViewRender : function(view) {
134020         var me = this;
134021
134022         if (me.enableDrag) {
134023             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
134024                 view: view,
134025                 ddGroup: me.dragGroup || me.ddGroup,
134026                 dragText: me.dragText,
134027                 repairHighlightColor: me.nodeHighlightColor,
134028                 repairHighlight: me.nodeHighlightOnRepair
134029             });
134030         }
134031
134032         if (me.enableDrop) {
134033             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
134034                 view: view,
134035                 ddGroup: me.dropGroup || me.ddGroup,
134036                 allowContainerDrops: me.allowContainerDrops,
134037                 appendOnly: me.appendOnly,
134038                 allowParentInserts: me.allowParentInserts,
134039                 expandDelay: me.expandDelay,
134040                 dropHighlightColor: me.nodeHighlightColor,
134041                 dropHighlight: me.nodeHighlightOnDrop
134042             });
134043         }
134044     }
134045 });
134046 /**
134047  * @class Ext.util.Cookies
134048
134049 Utility class for setting/reading values from browser cookies.
134050 Values can be written using the {@link #set} method.
134051 Values can be read using the {@link #get} method.
134052 A cookie can be invalidated on the client machine using the {@link #clear} method.
134053
134054  * @markdown
134055  * @singleton
134056  */
134057 Ext.define('Ext.util.Cookies', {
134058     singleton: true,
134059     
134060     /**
134061      * Create a cookie with the specified name and value. Additional settings
134062      * for the cookie may be optionally specified (for example: expiration,
134063      * access restriction, SSL).
134064      * @param {String} name The name of the cookie to set. 
134065      * @param {Object} value The value to set for the cookie.
134066      * @param {Object} expires (Optional) Specify an expiration date the
134067      * cookie is to persist until.  Note that the specified Date object will
134068      * be converted to Greenwich Mean Time (GMT). 
134069      * @param {String} path (Optional) Setting a path on the cookie restricts
134070      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
134071      * @param {String} domain (Optional) Setting a domain restricts access to
134072      * pages on a given domain (typically used to allow cookie access across
134073      * subdomains). For example, "sencha.com" will create a cookie that can be
134074      * accessed from any subdomain of sencha.com, including www.sencha.com,
134075      * support.sencha.com, etc.
134076      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
134077      * should only be accessible via SSL on a page using the HTTPS protocol.
134078      * Defaults to <tt>false</tt>. Note that this will only work if the page
134079      * calling this code uses the HTTPS protocol, otherwise the cookie will be
134080      * created with default options.
134081      */
134082     set : function(name, value){
134083         var argv = arguments,
134084             argc = arguments.length,
134085             expires = (argc > 2) ? argv[2] : null,
134086             path = (argc > 3) ? argv[3] : '/',
134087             domain = (argc > 4) ? argv[4] : null,
134088             secure = (argc > 5) ? argv[5] : false;
134089             
134090         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
134091     },
134092
134093     /**
134094      * Retrieves cookies that are accessible by the current page. If a cookie
134095      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
134096      * example retrieves the cookie called "valid" and stores the String value
134097      * in the variable <tt>validStatus</tt>.
134098      * <pre><code>
134099      * var validStatus = Ext.util.Cookies.get("valid");
134100      * </code></pre>
134101      * @param {String} name The name of the cookie to get
134102      * @return {Object} Returns the cookie value for the specified name;
134103      * null if the cookie name does not exist.
134104      */
134105     get : function(name){
134106         var arg = name + "=",
134107             alen = arg.length,
134108             clen = document.cookie.length,
134109             i = 0,
134110             j = 0;
134111             
134112         while(i < clen){
134113             j = i + alen;
134114             if(document.cookie.substring(i, j) == arg){
134115                 return this.getCookieVal(j);
134116             }
134117             i = document.cookie.indexOf(" ", i) + 1;
134118             if(i === 0){
134119                 break;
134120             }
134121         }
134122         return null;
134123     },
134124
134125     /**
134126      * Removes a cookie with the provided name from the browser
134127      * if found by setting its expiration date to sometime in the past. 
134128      * @param {String} name The name of the cookie to remove
134129      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
134130      */
134131     clear : function(name, path){
134132         if(this.get(name)){
134133             path = path || '/';
134134             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
134135         }
134136     },
134137     
134138     /**
134139      * @private
134140      */
134141     getCookieVal : function(offset){
134142         var endstr = document.cookie.indexOf(";", offset);
134143         if(endstr == -1){
134144             endstr = document.cookie.length;
134145         }
134146         return unescape(document.cookie.substring(offset, endstr));
134147     }
134148 });
134149
134150 /**
134151  * @class Ext.util.CSS
134152  * Utility class for manipulating CSS rules
134153  * @singleton
134154  */
134155 Ext.define('Ext.util.CSS', function() {
134156     var rules = null;
134157     var doc = document;
134158
134159     var camelRe = /(-[a-z])/gi;
134160     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
134161
134162     return {
134163
134164         singleton: true,
134165
134166         constructor: function() {
134167             this.rules = {};
134168             this.initialized = false;
134169         },
134170
134171         /**
134172          * Creates a stylesheet from a text blob of rules.
134173          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
134174          * @param {String} cssText The text containing the css rules
134175          * @param {String} id An id to add to the stylesheet for later removal
134176          * @return {CSSStyleSheet}
134177          */
134178         createStyleSheet : function(cssText, id) {
134179             var ss,
134180                 head = doc.getElementsByTagName("head")[0],
134181                 styleEl = doc.createElement("style");
134182
134183             styleEl.setAttribute("type", "text/css");
134184             if (id) {
134185                styleEl.setAttribute("id", id);
134186             }
134187
134188             if (Ext.isIE) {
134189                head.appendChild(styleEl);
134190                ss = styleEl.styleSheet;
134191                ss.cssText = cssText;
134192             } else {
134193                 try{
134194                     styleEl.appendChild(doc.createTextNode(cssText));
134195                 } catch(e) {
134196                    styleEl.cssText = cssText;
134197                 }
134198                 head.appendChild(styleEl);
134199                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
134200             }
134201             this.cacheStyleSheet(ss);
134202             return ss;
134203         },
134204
134205         /**
134206          * Removes a style or link tag by id
134207          * @param {String} id The id of the tag
134208          */
134209         removeStyleSheet : function(id) {
134210             var existing = document.getElementById(id);
134211             if (existing) {
134212                 existing.parentNode.removeChild(existing);
134213             }
134214         },
134215
134216         /**
134217          * Dynamically swaps an existing stylesheet reference for a new one
134218          * @param {String} id The id of an existing link tag to remove
134219          * @param {String} url The href of the new stylesheet to include
134220          */
134221         swapStyleSheet : function(id, url) {
134222             var doc = document;
134223             this.removeStyleSheet(id);
134224             var ss = doc.createElement("link");
134225             ss.setAttribute("rel", "stylesheet");
134226             ss.setAttribute("type", "text/css");
134227             ss.setAttribute("id", id);
134228             ss.setAttribute("href", url);
134229             doc.getElementsByTagName("head")[0].appendChild(ss);
134230         },
134231
134232         /**
134233          * Refresh the rule cache if you have dynamically added stylesheets
134234          * @return {Object} An object (hash) of rules indexed by selector
134235          */
134236         refreshCache : function() {
134237             return this.getRules(true);
134238         },
134239
134240         // private
134241         cacheStyleSheet : function(ss) {
134242             if(!rules){
134243                 rules = {};
134244             }
134245             try {// try catch for cross domain access issue
134246                 var ssRules = ss.cssRules || ss.rules,
134247                     selectorText,
134248                     i = ssRules.length - 1,
134249                     j,
134250                     selectors;
134251
134252                 for (; i >= 0; --i) {
134253                     selectorText = ssRules[i].selectorText;
134254                     if (selectorText) {
134255
134256                         // Split in case there are multiple, comma-delimited selectors
134257                         selectorText = selectorText.split(',');
134258                         selectors = selectorText.length;
134259                         for (j = 0; j < selectors; j++) {
134260                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
134261                         }
134262                     }
134263                 }
134264             } catch(e) {}
134265         },
134266
134267         /**
134268         * Gets all css rules for the document
134269         * @param {Boolean} refreshCache true to refresh the internal cache
134270         * @return {Object} An object (hash) of rules indexed by selector
134271         */
134272         getRules : function(refreshCache) {
134273             if (rules === null || refreshCache) {
134274                 rules = {};
134275                 var ds = doc.styleSheets,
134276                     i = 0,
134277                     len = ds.length;
134278
134279                 for (; i < len; i++) {
134280                     try {
134281                         if (!ds[i].disabled) {
134282                             this.cacheStyleSheet(ds[i]);
134283                         }
134284                     } catch(e) {}
134285                 }
134286             }
134287             return rules;
134288         },
134289
134290         /**
134291          * Gets an an individual CSS rule by selector(s)
134292          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
134293          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
134294          * @return {CSSStyleRule} The CSS rule or null if one is not found
134295          */
134296         getRule: function(selector, refreshCache) {
134297             var rs = this.getRules(refreshCache);
134298             if (!Ext.isArray(selector)) {
134299                 return rs[selector.toLowerCase()];
134300             }
134301             for (var i = 0; i < selector.length; i++) {
134302                 if (rs[selector[i]]) {
134303                     return rs[selector[i].toLowerCase()];
134304                 }
134305             }
134306             return null;
134307         },
134308
134309         /**
134310          * Updates a rule property
134311          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
134312          * @param {String} property The css property
134313          * @param {String} value The new value for the property
134314          * @return {Boolean} true If a rule was found and updated
134315          */
134316         updateRule : function(selector, property, value){
134317             if (!Ext.isArray(selector)) {
134318                 var rule = this.getRule(selector);
134319                 if (rule) {
134320                     rule.style[property.replace(camelRe, camelFn)] = value;
134321                     return true;
134322                 }
134323             } else {
134324                 for (var i = 0; i < selector.length; i++) {
134325                     if (this.updateRule(selector[i], property, value)) {
134326                         return true;
134327                     }
134328                 }
134329             }
134330             return false;
134331         }
134332     };
134333 }());
134334 /**
134335  * @class Ext.util.History
134336  *
134337  * History management component that allows you to register arbitrary tokens that signify application
134338  * history state on navigation actions.  You can then handle the history {@link #change} event in order
134339  * to reset your application UI to the appropriate state when the user navigates forward or backward through
134340  * the browser history stack.
134341  *
134342  * ## Initializing
134343  * The {@link #init} method of the History object must be called before using History. This sets up the internal
134344  * state and must be the first thing called before using History.
134345  *
134346  * ## Setup
134347  * The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
134348  * an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
134349  * these to be on the page before the {@link #init} method is called. The following markup is suggested in order
134350  * to support all browsers:
134351  *
134352  *     <form id="history-form" class="x-hide-display">
134353  *         <input type="hidden" id="x-history-field" />
134354  *         <iframe id="x-history-frame"></iframe>
134355  *     </form>
134356  *
134357  * @singleton
134358  */
134359 Ext.define('Ext.util.History', {
134360     singleton: true,
134361     alternateClassName: 'Ext.History',
134362     mixins: {
134363         observable: 'Ext.util.Observable'
134364     },
134365
134366     constructor: function() {
134367         var me = this;
134368         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
134369         me.iframe = null;
134370         me.hiddenField = null;
134371         me.ready = false;
134372         me.currentToken = null;
134373     },
134374
134375     getHash: function() {
134376         var href = window.location.href,
134377             i = href.indexOf("#");
134378
134379         return i >= 0 ? href.substr(i + 1) : null;
134380     },
134381
134382     doSave: function() {
134383         this.hiddenField.value = this.currentToken;
134384     },
134385
134386
134387     handleStateChange: function(token) {
134388         this.currentToken = token;
134389         this.fireEvent('change', token);
134390     },
134391
134392     updateIFrame: function(token) {
134393         var html = '<html><body><div id="state">' +
134394                     Ext.util.Format.htmlEncode(token) +
134395                     '</div></body></html>';
134396
134397         try {
134398             var doc = this.iframe.contentWindow.document;
134399             doc.open();
134400             doc.write(html);
134401             doc.close();
134402             return true;
134403         } catch (e) {
134404             return false;
134405         }
134406     },
134407
134408     checkIFrame: function () {
134409         var me = this,
134410             contentWindow = me.iframe.contentWindow;
134411
134412         if (!contentWindow || !contentWindow.document) {
134413             Ext.Function.defer(this.checkIFrame, 10, this);
134414             return;
134415         }
134416
134417         var doc = contentWindow.document,
134418             elem = doc.getElementById("state"),
134419             oldToken = elem ? elem.innerText : null,
134420             oldHash = me.getHash();
134421
134422         Ext.TaskManager.start({
134423             run: function () {
134424                 var doc = contentWindow.document,
134425                     elem = doc.getElementById("state"),
134426                     newToken = elem ? elem.innerText : null,
134427                     newHash = me.getHash();
134428
134429                 if (newToken !== oldToken) {
134430                     oldToken = newToken;
134431                     me.handleStateChange(newToken);
134432                     window.top.location.hash = newToken;
134433                     oldHash = newToken;
134434                     me.doSave();
134435                 } else if (newHash !== oldHash) {
134436                     oldHash = newHash;
134437                     me.updateIFrame(newHash);
134438                 }
134439             },
134440             interval: 50,
134441             scope: me
134442         });
134443         me.ready = true;
134444         me.fireEvent('ready', me);
134445     },
134446
134447     startUp: function () {
134448         var me = this;
134449
134450         me.currentToken = me.hiddenField.value || this.getHash();
134451
134452         if (me.oldIEMode) {
134453             me.checkIFrame();
134454         } else {
134455             var hash = me.getHash();
134456             Ext.TaskManager.start({
134457                 run: function () {
134458                     var newHash = me.getHash();
134459                     if (newHash !== hash) {
134460                         hash = newHash;
134461                         me.handleStateChange(hash);
134462                         me.doSave();
134463                     }
134464                 },
134465                 interval: 50,
134466                 scope: me
134467             });
134468             me.ready = true;
134469             me.fireEvent('ready', me);
134470         }
134471
134472     },
134473
134474     /**
134475      * The id of the hidden field required for storing the current history token.
134476      * @type String
134477      * @property
134478      */
134479     fieldId: Ext.baseCSSPrefix + 'history-field',
134480     /**
134481      * The id of the iframe required by IE to manage the history stack.
134482      * @type String
134483      * @property
134484      */
134485     iframeId: Ext.baseCSSPrefix + 'history-frame',
134486
134487     /**
134488      * Initialize the global History instance.
134489      * @param {Boolean} onReady (optional) A callback function that will be called once the history
134490      * component is fully initialized.
134491      * @param {Object} scope (optional) The scope (`this` reference) in which the callback is executed. Defaults to the browser window.
134492      */
134493     init: function (onReady, scope) {
134494         var me = this;
134495
134496         if (me.ready) {
134497             Ext.callback(onReady, scope, [me]);
134498             return;
134499         }
134500
134501         if (!Ext.isReady) {
134502             Ext.onReady(function() {
134503                 me.init(onReady, scope);
134504             });
134505             return;
134506         }
134507
134508         me.hiddenField = Ext.getDom(me.fieldId);
134509
134510         if (me.oldIEMode) {
134511             me.iframe = Ext.getDom(me.iframeId);
134512         }
134513
134514         me.addEvents(
134515             /**
134516              * @event ready
134517              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
134518              * @param {Ext.util.History} The Ext.util.History singleton.
134519              */
134520             'ready',
134521             /**
134522              * @event change
134523              * Fires when navigation back or forwards within the local page's history occurs.
134524              * @param {String} token An identifier associated with the page state at that point in its history.
134525              */
134526             'change'
134527         );
134528
134529         if (onReady) {
134530             me.on('ready', onReady, scope, {single: true});
134531         }
134532         me.startUp();
134533     },
134534
134535     /**
134536      * Add a new token to the history stack. This can be any arbitrary value, although it would
134537      * commonly be the concatenation of a component id and another id marking the specific history
134538      * state of that component. Example usage:
134539      *
134540      *     // Handle tab changes on a TabPanel
134541      *     tabPanel.on('tabchange', function(tabPanel, tab){
134542      *          Ext.History.add(tabPanel.id + ':' + tab.id);
134543      *     });
134544      *
134545      * @param {String} token The value that defines a particular application-specific history state
134546      * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
134547      * it will not save a new history step. Set to false if the same state can be saved more than once
134548      * at the same history stack location.
134549      */
134550     add: function (token, preventDup) {
134551         var me = this;
134552
134553         if (preventDup !== false) {
134554             if (me.getToken() === token) {
134555                 return true;
134556             }
134557         }
134558
134559         if (me.oldIEMode) {
134560             return me.updateIFrame(token);
134561         } else {
134562             window.top.location.hash = token;
134563             return true;
134564         }
134565     },
134566
134567     /**
134568      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
134569      */
134570     back: function() {
134571         window.history.go(-1);
134572     },
134573
134574     /**
134575      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
134576      */
134577     forward: function(){
134578         window.history.go(1);
134579     },
134580
134581     /**
134582      * Retrieves the currently-active history token.
134583      * @return {String} The token
134584      */
134585     getToken: function() {
134586         return this.ready ? this.currentToken : this.getHash();
134587     }
134588 });
134589 /**
134590  * @class Ext.view.TableChunker
134591  * 
134592  * Produces optimized XTemplates for chunks of tables to be
134593  * used in grids, trees and other table based widgets.
134594  *
134595  * @singleton
134596  */
134597 Ext.define('Ext.view.TableChunker', {
134598     singleton: true,
134599     requires: ['Ext.XTemplate'],
134600     metaTableTpl: [
134601         '{[this.openTableWrap()]}',
134602         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
134603             '<tbody>',
134604             '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
134605             '<tpl for="columns">',
134606                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
134607             '</tpl>',
134608             '</tr>',
134609             '{[this.openRows()]}',
134610                 '{row}',
134611                 '<tpl for="features">',
134612                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
134613                 '</tpl>',
134614             '{[this.closeRows()]}',
134615             '</tbody>',
134616         '</table>',
134617         '{[this.closeTableWrap()]}'
134618     ],
134619
134620     constructor: function() {
134621         Ext.XTemplate.prototype.recurse = function(values, reference) {
134622             return this.apply(reference ? values[reference] : values);
134623         };
134624     },
134625
134626     embedFeature: function(values, parent, x, xcount) {
134627         var tpl = '';
134628         if (!values.disabled) {
134629             tpl = values.getFeatureTpl(values, parent, x, xcount);
134630         }
134631         return tpl;
134632     },
134633
134634     embedFullWidth: function() {
134635         return 'style="width: {fullWidth}px;"';
134636     },
134637
134638     openRows: function() {
134639         return '<tpl for="rows">';
134640     },
134641
134642     closeRows: function() {
134643         return '</tpl>';
134644     },
134645
134646     metaRowTpl: [
134647         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
134648             '<tpl for="columns">',
134649                 '<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>',
134650             '</tpl>',
134651         '</tr>'
134652     ],
134653     
134654     firstOrLastCls: function(xindex, xcount) {
134655         var cssCls = '';
134656         if (xindex === 1) {
134657             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
134658         } else if (xindex === xcount) {
134659             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
134660         }
134661         return cssCls;
134662     },
134663     
134664     embedRowCls: function() {
134665         return '{rowCls}';
134666     },
134667     
134668     embedRowAttr: function() {
134669         return '{rowAttr}';
134670     },
134671     
134672     openTableWrap: function() {
134673         return '';
134674     },
134675     
134676     closeTableWrap: function() {
134677         return '';
134678     },
134679
134680     getTableTpl: function(cfg, textOnly) {
134681         var tpl,
134682             tableTplMemberFns = {
134683                 openRows: this.openRows,
134684                 closeRows: this.closeRows,
134685                 embedFeature: this.embedFeature,
134686                 embedFullWidth: this.embedFullWidth,
134687                 openTableWrap: this.openTableWrap,
134688                 closeTableWrap: this.closeTableWrap
134689             },
134690             tplMemberFns = {},
134691             features = cfg.features || [],
134692             ln = features.length,
134693             i  = 0,
134694             memberFns = {
134695                 embedRowCls: this.embedRowCls,
134696                 embedRowAttr: this.embedRowAttr,
134697                 firstOrLastCls: this.firstOrLastCls
134698             },
134699             // copy the default
134700             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
134701             metaTableTpl;
134702             
134703         for (; i < ln; i++) {
134704             if (!features[i].disabled) {
134705                 features[i].mutateMetaRowTpl(metaRowTpl);
134706                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
134707                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
134708                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
134709             }
134710         }
134711         
134712         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
134713         cfg.row = metaRowTpl.applyTemplate(cfg);
134714         
134715         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
134716         
134717         tpl = metaTableTpl.applyTemplate(cfg);
134718         
134719         // TODO: Investigate eliminating.
134720         if (!textOnly) {
134721             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
134722         }
134723         return tpl;
134724         
134725     }
134726 });
134727
134728
134729
134730